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 2599 additions and 0 deletions
+2599 -0
grammar Keywords;
// These are the keywords that NanoDB recognizes. As usual, the set definitely
// overlaps standard SQL, and it also includes other non-standard commands.
ADD : [Aa][Dd][Dd] ;
ALL : [Aa][Ll][Ll] ;
ALTER : [Aa][Ll][Tt][Ee][Rr] ;
ANALYZE : [Aa][Nn][Aa][Ll][Yy][Zz][Ee] ;
AND : [Aa][Nn][Dd] ;
ANY : [Aa][Nn][Yy] ;
AS : [Aa][Ss] ;
ASC : [Aa][Ss][Cc] ;
ASCENDING : [Aa][Ss][Cc][Ee][Nn][Dd][Ii][Nn][Gg] ;
BEGIN : [Bb][Ee][Gg][Ii][Nn] ;
BETWEEN : [Bb][Ee][Tt][Ww][Ee][Ee][Nn] ;
BY : [Bb][Yy] ;
CASCADE : [Cc][Aa][Ss][Cc][Aa][Dd][Ee] ;
CHECK : [Cc][Hh][Ee][Cc][Kk] ;
COLUMN : [Cc][Oo][Ll][Uu][Mm][Nn] ;
COMMIT : [Cc][Oo][Mm][Mm][Ii][Tt] ;
CONSTRAINT : [Cc][Oo][Nn][Ss][Tt][Rr][Aa][Ii][Nn][Tt] ;
CRASH : [Cc][Rr][Aa][Ss][Hh] ;
CREATE : [Cc][Rr][Ee][Aa][Tt][Ee] ;
CROSS : [Cc][Rr][Oo][Ss][Ss] ;
DEFAULT : [Dd][Ee][Ff][Aa][Uu][Ll][Tt] ;
DELETE : [Dd][Ee][Ll][Ee][Tt][Ee] ;
DESC : [Dd][Ee][Ss][Cc] ;
DESCENDING : [Dd][Ee][Ss][Cc][Ee][Nn][Dd][Ii][Nn][Gg] ;
DISTINCT : [Dd][Ii][Ss][Tt][Ii][Nn][Cc][Tt] ;
DROP : [Dd][Rr][Oo][Pp] ;
DUMP : [Dd][Uu][Mm][Pp] ;
EXCEPT : [Ee][Xx][Cc][Ee][Pp][Tt] ;
EXISTS : [Ee][Xx][Ii][Ss][Tt][Ss] ;
EXIT : [Ee][Xx][Ii][Tt] ;
EXPLAIN : [Ee][Xx][Pp][Ll][Aa][Ii][Nn] ;
FALSE : [Ff][Aa][Ll][Ss][Ee] ;
FILE : [Ff][Ii][Ll][Ee] ;
FLUSH : [Ff][Ll][Uu][Ss][Hh] ;
FOREIGN : [Ff][Oo][Rr][Ee][Ii][Gg][Nn] ;
FORMAT : [Ff][Oo][Rr][Mm][Aa][Tt] ;
FROM : [Ff][Rr][Oo][Mm] ;
FULL : [Ff][Uu][Ll][Ll] ;
GROUP : [Gg][Rr][Oo][Uu][Pp] ;
HAVING : [Hh][Aa][Vv][Ii][Nn][Gg] ;
IF : [Ii][Ff] ;
IN : [Ii][Nn] ;
INDEX : [Ii][Nn][Dd][Ee][Xx] ;
INNER : [Ii][Nn][Nn][Ee][Rr] ;
INSERT : [Ii][Nn][Ss][Ee][Rr][Tt] ;
INTERSECT : [Ii][Nn][Tt][Ee][Rr][Ss][Ee][Cc][Tt] ;
INTERVAL : [Ii][Nn][Tt][Ee][Rr][Vv][Aa][Ll] ;
INTO : [Ii][Nn][Tt][Oo] ;
IS : [Ii][Ss] ;
JOIN : [Jj][Oo][Ii][Nn] ;
KEY : [Kk][Ee][Yy] ;
LEFT : [Ll][Ee][Ff][Tt] ;
LIKE : [Ll][Ii][Kk][Ee] ;
LIMIT : [Ll][Ii][Mm][Ii][Tt] ;
MINUS : [Mm][Ii][Nn][Uu][Ss] ;
NATURAL : [Nn][Aa][Tt][Uu][Rr][Aa][Ll] ;
NOT : [Nn][Oo][Tt] ;
NULL : [Nn][Uu][Ll][Ll] ;
OFFSET : [Oo][Ff][Ff][Ss][Ee][Tt] ;
ON : [Oo][Nn] ;
OPTIMIZE : [Oo][Pp][Tt][Ii][Mm][Ii][Zz][Ee] ;
OR : [Oo][Rr] ;
ORDER : [Oo][Rr][Dd][Ee][Rr] ;
OUTER : [Oo][Uu][Tt][Ee][Rr] ;
PRIMARY : [Pp][Rr][Ii][Mm][Aa][Rr][Yy] ;
PROPERTIES : [Pp][Rr][Oo][Pp][Ee][Rr][Tt][Ii][Ee][Ss] ;
PROPERTY : [Pp][Rr][Oo][Pp][Ee][Rr][Tt][Yy] ;
QUIT : [Qq][Uu][Ii][Tt] ;
RECURSIVE : [Rr][Ee][Cc][Uu][Rr][Ss][Ii][Vv][Ee] ;
REFERENCES : [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee][Ss] ;
RENAME : [Rr][Ee][Nn][Aa][Mm][Ee] ;
RESTRICT : [Rr][Ee][Ss][Tt][Rr][Ii][Cc][Tt] ;
RIGHT : [Rr][Ii][Gg][Hh][Tt] ;
ROLLBACK : [Rr][Oo][Ll][Ll][Bb][Aa][Cc][Kk] ;
SELECT : [Ss][Ee][Ll][Ee][Cc][Tt] ;
SET : [Ss][Ee][Tt] ;
SHOW : [Ss][Hh][Oo][Ww] ;
SIMILAR : [Ss][Ii][Mm][Ii][Ll][Aa][Rr] ;
SOME : [Ss][Oo][Mm][Ee] ;
START : [Ss][Tt][Aa][Rr][Tt] ;
STATS : [Ss][Tt][Aa][Tt][Ss] ;
TABLE : [Tt][Aa][Bb][Ll][Ee] ;
TABLES : [Tt][Aa][Bb][Ll][Ee][Ss] ;
TEMPORARY : [Tt][Ee][Mm][Pp][Oo][Rr][Aa][Rr][Yy] ;
TO : [Tt][Oo] ;
TRANSACTION : [Tt][Rr][Aa][Nn][Ss][Aa][Cc][Tt][Ii][Oo][Nn] ;
TRUE : [Tt][Rr][Uu][Ee] ;
TYPE : [Tt][Yy][Pp][Ee] ;
UNION : [Uu][Nn][Ii][Oo][Nn] ;
UNIQUE : [Uu][Nn][Ii][Qq][Uu][Ee] ;
UNKNOWN : [Uu][Nn][Kk][Nn][Oo][Ww][Nn] ;
UPDATE : [Uu][Pp][Dd][Aa][Tt][Ee] ;
USING : [Uu][Ss][Ii][Nn][Gg] ;
VALUES : [Vv][Aa][Ll][Uu][Ee][Ss] ;
VERBOSE : [Vv][Ee][Rr][Bb][Oo][Ss][Ee] ;
VERIFY : [Vv][Ee][Rr][Ii][Ff][Yy] ;
VIEW : [Vv][Ii][Ee][Ww] ;
WHERE : [Ww][Hh][Ee][Rr][Ee] ;
WITH : [Ww][Ii][Tt][Hh] ;
WORK : [Ww][Oo][Rr][Kk] ;
// These tokens are for type-recognition. A number of these types have
// additional syntax for specifying length or precision, which is why we have
// parser rules for them. The type-system is not extensible by database users.
// Note also that not all of these types are supported by NanoDB; they are
// primarily included to reserve the keywords.
TYPE_BIGINT : [Bb][Ii][Gg][Ii][Nn][Tt] ;
TYPE_BLOB : [Bb][Ll][Oo][Bb] ;
TYPE_CHAR : [Cc][Hh][Aa][Rr] ; // char(length)
TYPE_CHARACTER : [Cc][Hh][Aa][Rr][Aa][Cc][Tt][Ee][Rr] ; // character(length)
TYPE_DATE : [Dd][Aa][Tt][Ee] ;
TYPE_DATETIME : [Dd][Aa][Tt][Ee][Tt][Ii][Mm][Ee] ;
TYPE_DECIMAL : [Dd][Ee][Cc][Ii][Mm][Aa][Ll] ; // decimal, decimal(prec), decimal(prec, scale)
TYPE_FLOAT : [Ff][Ll][Oo][Aa][Tt] ; // float, float(prec)
TYPE_DOUBLE : [Dd][Oo][Uu][Bb][Ll][Ee] ; // double
TYPE_INT : [Ii][Nn][Tt] ;
TYPE_INTEGER : [Ii][Nn][Tt][Ee][Gg][Ee][Rr] ;
TYPE_NUMERIC : [Nn][Uu][Mm][Ee][Rr][Ii][Cc] ; // numeric, numeric(prec), numeric(prec, scale)
TYPE_SMALLINT : [Ss][Mm][Aa][Ll][Ll][Ii][Nn][Tt] ;
TYPE_TEXT : [Tt][Ee][Xx][Tt] ;
TYPE_TIME : [Tt][Ii][Mm][Ee] ;
TYPE_TIMESTAMP : [Tt][Ii][Mm][Ee][Ss][Tt][Aa][Mm][Pp] ;
TYPE_TINYINT : [Tt][Ii][Nn][Yy][Ii][Nn][Tt] ;
TYPE_VARCHAR : [Vv][Aa][Rr][Cc][Hh][Aa][Rr] ; // varchar(length)
TYPE_VARYING : [Vv][Aa][Rr][Yy][Ii][Nn][Gg] ; // character varying(length)
grammar Keywords;
// These are the keywords that NanoDB recognizes. As usual, the set definitely
// overlaps standard SQL, and it also includes other non-standard commands.
ADD : 'add' ;
ALL : 'all' ;
ALTER : 'alter' ;
ANALYZE : 'analyze' ;
AND : 'and' ;
ANY : 'any' ;
AS : 'as' ;
ASC : 'asc' ;
ASCENDING : 'ascending' ;
BEGIN : 'begin' ;
BETWEEN : 'between' ;
BY : 'by' ;
CASCADE : 'cascade' ;
CHECK : 'check' ;
COLUMN : 'column' ;
COMMIT : 'commit' ;
CONSTRAINT : 'constraint' ;
CRASH : 'crash' ;
CREATE : 'create' ;
CROSS : 'cross' ;
DEFAULT : 'default' ;
DELETE : 'delete' ;
DESC : 'desc' ;
DESCENDING : 'descending' ;
DISTINCT : 'distinct' ;
DROP : 'drop' ;
DUMP : 'dump' ;
EXCEPT : 'except' ;
EXISTS : 'exists' ;
EXIT : 'exit' ;
EXPLAIN : 'explain' ;
FALSE : 'false' ;
FILE : 'file' ;
FLUSH : 'flush' ;
FOREIGN : 'foreign' ;
FORMAT : 'format' ;
FROM : 'from' ;
FULL : 'full' ;
GROUP : 'group' ;
HAVING : 'having' ;
IF : 'if' ;
IN : 'in' ;
INDEX : 'index' ;
INNER : 'inner' ;
INSERT : 'insert' ;
INTERSECT : 'intersect' ;
INTERVAL : 'interval' ;
INTO : 'into' ;
IS : 'is' ;
JOIN : 'join' ;
KEY : 'key' ;
LEFT : 'left' ;
LIKE : 'like' ;
LIMIT : 'limit' ;
MINUS : 'minus' ;
NATURAL : 'natural' ;
NOT : 'not' ;
NULL : 'null' ;
OFFSET : 'offset' ;
ON : 'on' ;
OPTIMIZE : 'optimize' ;
OR : 'or' ;
ORDER : 'order' ;
OUTER : 'outer' ;
PRIMARY : 'primary' ;
PROPERTIES : 'properties' ;
PROPERTY : 'property' ;
QUIT : 'quit' ;
RECURSIVE : 'recursive' ;
REFERENCES : 'references' ;
RENAME : 'rename' ;
RESTRICT : 'restrict' ;
RIGHT : 'right' ;
ROLLBACK : 'rollback' ;
SELECT : 'select' ;
SET : 'set' ;
SHOW : 'show' ;
SIMILAR : 'similar' ;
SOME : 'some' ;
START : 'start' ;
STATS : 'stats' ;
TABLE : 'table' ;
TABLES : 'tables' ;
TEMPORARY : 'temporary' ;
TO : 'to' ;
TRANSACTION : 'transaction' ;
TRUE : 'true' ;
TYPE : 'type' ;
UNION : 'union' ;
UNIQUE : 'unique' ;
UNKNOWN : 'unknown' ;
UPDATE : 'update' ;
USING : 'using' ;
VALUES : 'values' ;
VERBOSE : 'verbose' ;
VERIFY : 'verify' ;
VIEW : 'view' ;
WHERE : 'where' ;
WITH : 'with' ;
WORK : 'work' ;
// These tokens are for type-recognition. A number of these types have
// additional syntax for specifying length or precision, which is why we have
// parser rules for them. The type-system is not extensible by database users.
// Note also that not all of these types are supported by NanoDB; they are
// primarily included to reserve the keywords.
TYPE_BIGINT : 'bigint' ;
TYPE_BLOB : 'blob' ;
TYPE_CHAR : 'char' ; // char(length)
TYPE_CHARACTER : 'character' ; // character(length)
TYPE_DATE : 'date' ;
TYPE_DATETIME : 'datetime' ;
TYPE_DECIMAL : 'decimal' ; // decimal, decimal(prec), decimal(prec, scale)
TYPE_FLOAT : 'float' ; // float, float(prec)
TYPE_DOUBLE : 'double' ; // double
TYPE_INT : 'int' ;
TYPE_INTEGER : 'integer' ;
TYPE_NUMERIC : 'numeric' ; // numeric, numeric(prec), numeric(prec, scale)
TYPE_SMALLINT : 'smallint' ;
TYPE_TEXT : 'text' ;
TYPE_TIME : 'time' ;
TYPE_TIMESTAMP : 'timestamp' ;
TYPE_TINYINT : 'tinyint' ;
TYPE_VARCHAR : 'varchar' ; // varchar(length)
TYPE_VARYING : 'varying' ; // character varying(length)
grammar Literals;
//============================================================================
// Literals
// We keep literals pretty simple in NanoDB. Values can be single-quoted
// strings, or integer values (converted to Integer/Long/BigInteger as
// necessary for precision), or decimal values (converted to BigDecimal,
// but also castable to float or double as necessary).
//
IDENT:
[A-Za-z_] [A-Za-z0-9_]* ;
INT_LITERAL:
[0-9]+ ;
DECIMAL_LITERAL:
[0-9]+ '.' [0-9]* | '.' [0-9]+ ;
STRING_LITERAL:
'\'' .*? '\'' ;
//============================================================================
// Stuff We Ignore
//
// Define whitespace rule, toss it out
WS:
[ \t\r\n]+ -> skip ;
// Toss out comments as well
COMMENT:
'/*' .*? '*/' -> skip ;
LINE_COMMENT:
'--' .*? '\n' -> skip ;
import sys
def make_insensitive(filename):
if not filename.endswith('.g4.in'):
raise ValueError('Unrecognized file-type for %s' % filename)
# Chop off the ".in" part
out_filename = filename[:-3]
with open(filename) as inp, open(out_filename, 'w') as out:
while True:
line = inp.readline()
if line == '':
break
i_comment = line.find("//")
i_quote = line.find("'")
if i_quote != -1 and (i_comment == -1 or i_quote < i_comment):
i_endquote = line.find("'", i_quote+1)
if i_endquote != -1:
text = line[i_quote+1:i_endquote]
text = text.upper()
insens = ['[%c%c]' % (ch, ch.lower()) for ch in text]
insens = ''.join(insens)
line = line[:i_quote] + insens + line[i_endquote+1:]
out.write(line)
if len(sys.argv) == 1:
print('usage: %s file1.g4.in [file2.g4.in ...]' % sys.argv[0])
print('\tConverts the ANTLRv4 keyword file(s) to be case-insensitive.')
print('\tRequires at least one file to be specified on the command-line.')
sys.exit(1)
for filename in sys.argv[1:]:
if not filename.endswith('.g4.in'):
print('WARNING: Unrecognized file-type for %s, skipping.' % filename)
continue
make_insensitive(filename)
package edu.caltech.nanodb.client;
import java.io.IOException;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.commands.Command;
import edu.caltech.nanodb.server.CommandResult;
import edu.caltech.nanodb.server.NanoDBException;
import edu.caltech.nanodb.server.NanoDBServer;
import org.antlr.v4.runtime.misc.ParseCancellationException;
/**
* <p>
* This class is used for starting the NanoDB database in "exclusive mode,"
* where only a single client interacts directly with the database system.
* In fact, the exclusive client embeds the {@link NanoDBServer} instance in
* the client object, since there is no need to interact with the server over
* a network socket.
* </p>
* <p>
* <b>Note that it is <u>wrong</u> to start multiple exclusive clients against
* the same data directory!!!</b> Exclusive-mode operation expects that only
* this server is interacting with the data files. For concurrent access from
* multiple clients, see the {@link SharedServerClient} class.
* </p>
*/
public class ExclusiveClient extends InteractiveClient {
private static Logger logger = LogManager.getLogger(ExclusiveClient.class);
/** The server that this exclusive client is using. */
private NanoDBServer server;
@Override
public void startup() {
// Start up the various database subsystems that require initialization.
server = new NanoDBServer();
try {
server.startup();
}
catch (NanoDBException e) {
System.out.println("DATABASE STARTUP FAILED:");
e.printStackTrace(System.out);
System.exit(1);
}
}
@Override
public CommandResult handleCommand(String command) {
CommandResult result = server.doCommand(command, false);
if (result.failed()) {
Exception e = result.getFailure();
if (e instanceof ParseCancellationException) {
System.out.println("ERROR: Could not parse command");
}
else {
System.out.println("ERROR: " + e.getMessage());
}
}
return result;
}
@Override
public void shutdown() {
// Shut down the various database subsystems that require cleanup.
if (!server.shutdown())
System.out.println("DATABASE SHUTDOWN FAILED.");
}
public static void main(String args[]) {
ExclusiveClient client = new ExclusiveClient();
client.startup();
client.mainloop();
client.shutdown();
}
}
package edu.caltech.nanodb.client;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.server.CommandResult;
/**
* This abstract class implements the basic functionality necessary for
* providing an interactive SQL client.
*/
public abstract class InteractiveClient {
private static Logger logger = LogManager.getLogger(InteractiveClient.class);
/** A string constant specifying the "first-line" command-prompt. */
private static final String CMDPROMPT_FIRST = "CMD> ";
/** A string constant specifying the "subsequent-lines" command-prompt. */
private static final String CMDPROMPT_NEXT = " > ";
/** The buffer that accumulates each command's text. */
private StringBuilder enteredText;
public abstract void startup() throws Exception;
public void mainloop() {
// We don't use the console directly, since we can't read/write it
// if someone redirects a file onto the client's input-stream.
boolean hasConsole = (System.console() != null);
if (hasConsole) {
System.out.println(
"Welcome to NanoDB. Exit with EXIT or QUIT command.\n");
}
boolean exiting = false;
BufferedReader bufReader = new BufferedReader(new InputStreamReader(System.in));
while (!exiting) {
enteredText = new StringBuilder();
boolean firstLine = true;
getcmd:
while (true) {
try {
if (hasConsole) {
if (firstLine) {
System.out.print(CMDPROMPT_FIRST);
System.out.flush();
firstLine = false;
}
else {
System.out.print(CMDPROMPT_NEXT);
System.out.flush();
}
}
String line = bufReader.readLine();
if (line == null) {
// Hit EOF.
exiting = true;
break;
}
enteredText.append(line).append('\n');
// Process any commands in the entered text.
while (true) {
String command = getCommandString();
if (command == null)
break; // No more complete commands.
// if (logger.isDebugEnabled())
// logger.debug("Command string:\n" + command);
CommandResult result = handleCommand(command);
if (result.isExit()) {
exiting = true;
break getcmd;
}
}
if (enteredText.length() == 0)
firstLine = true;
}
catch (Throwable e) {
System.out.println("Unexpected error: " + e.getClass() +
": " + e.getMessage());
logger.error("Unexpected error", e);
}
}
}
}
/**
* This helper method goes through the {@link #enteredText} buffer, trying
* to identify the extent of the next command string. This is done using
* semicolons (that are not enclosed with single or double quotes). If a
* command is identified, it is removed from the internal buffer and
* returned. If no complete command is identified, {@code null} is
* returned.
*
* @return the first semicolon-terminated command in the internal data
* buffer, or {@code null} if the buffer contains no complete
* commands.
*/
private String getCommandString() {
int i = 0;
String command = null;
while (i < enteredText.length()) {
char ch = enteredText.charAt(i);
if (ch == ';') {
// Found the end of the command. Extract the string, and
// make sure the semicolon is also included.
command = enteredText.substring(0, i + 1);
enteredText.delete(0, i + 1);
// Consume any leading whitespace at the start of the entered
// text.
while (enteredText.length() > 0 &&
Character.isWhitespace(enteredText.charAt(0))) {
enteredText.deleteCharAt(0);
}
break;
}
else if (ch == '\'' || ch == '"') {
// Need to ignore all subsequent characters until we find
// the end of this quoted string.
i++;
while (i < enteredText.length() &&
enteredText.charAt(i) != ch) {
i++;
}
}
i++; // Go on to the next character.
}
return command;
}
/**
* Subclasses can implement this method to handle each command entered
* by the user. For example, a subclass may send the command over a
* socket to the server, wait for a response, then output the response
* to the console.
*
* @param command the command to handle.
*
* @return the command-result from executing the command
*/
public abstract CommandResult handleCommand(String command);
/**
* Shut down the interactive client. The specific way the client
* interacts with the server dictates how this shutdown mechanism
* will work.
*
* @throws Exception if any error occurs during shutdown
*/
public abstract void shutdown() throws Exception;
}
package edu.caltech.nanodb.client;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.ClosedByInterruptException;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.expressions.TupleLiteral;
import edu.caltech.nanodb.queryeval.PrettyTuplePrinter;
import edu.caltech.nanodb.relations.Schema;
import edu.caltech.nanodb.relations.Tuple;
import edu.caltech.nanodb.server.CommandState;
/**
* This class represents
*
*/
public class SharedClientState {
private static Logger logger = LogManager.getLogger(SharedClientState.class);
private Socket socket;
private ObjectInputStream objectInput;
private Receiver receiver;
/**
* This thread receives data from the server asynchronously, and prints
* out whatever it receives.
*/
private Thread receiverThread;
private PrintStream out;
private boolean printTuples;
private Semaphore semCommandDone;
private Schema schema;
private ArrayList<TupleLiteral> tuples;
/**
* This stream is used to send objects (commands, specifically) to the
*/
private ObjectOutputStream objectOutput;
private class Receiver implements Runnable {
private PrintStream out;
private boolean done;
public Receiver(PrintStream out) {
this.out = out;
}
public void run() {
PrettyTuplePrinter tuplePrinter = null;
done = false;
while (true) {
try {
Object obj = objectInput.readObject();
if (obj instanceof String) {
// Just print strings to the console
System.out.print(obj);
}
else if (obj instanceof Schema) {
tuplePrinter = new PrettyTuplePrinter(out);
tuplePrinter.setSchema((Schema) obj);
}
else if (obj instanceof Tuple) {
tuplePrinter.process((Tuple) obj);
}
else if (obj instanceof Throwable) {
Throwable t = (Throwable) obj;
t.printStackTrace(System.out);
}
else if (obj instanceof CommandState) {
CommandState state = (CommandState) obj;
if (state == CommandState.COMMAND_COMPLETED) {
if (tuplePrinter != null) {
tuplePrinter.finish();
tuplePrinter = null;
}
// Signal that the command is completed.
semCommandDone.release();
}
}
else {
// TODO: Try to print...
System.out.println(obj);
}
} catch (EOFException e) {
System.out.println("Connection was closed by the server.");
break;
} catch (SocketException e) {
System.out.println("Connection was closed by the server.");
break;
} catch (ClosedByInterruptException e) {
System.out.println("Thread was interrupted during an IO operation.");
break;
} catch (Exception e) {
System.out.println("Exception occurred:");
e.printStackTrace(System.out);
}
}
}
public void shutdown() {
done = true;
}
}
public SharedClientState(PrintStream out, boolean printTuples) {
this.out = out;
this.printTuples = printTuples;
}
public void connect(String hostname, int port) throws IOException {
// Try to establish a connection to the shared database server.
socket = new Socket(hostname, port);
objectOutput = new ObjectOutputStream(socket.getOutputStream());
objectInput = new ObjectInputStream(socket.getInputStream());
// A semaphore to synchronize the receiver with the code that
// dispatches commands, so that we don't return from dispatching a
// command until the server says the command is finished.
semCommandDone = new Semaphore(0);
// Start up the receiver thread that will print out whatever comes
// across the wire.
receiver = new Receiver(System.out);
receiverThread = new Thread(receiver);
receiverThread.start();
}
public void doCommand(String commandString) throws Exception {
schema = null;
tuples.clear();
// Send the command to the server!
objectOutput.writeObject(commandString);
// Wait for the command to be completed.
semCommandDone.acquire();
}
public void disconnect() throws IOException {
receiver.shutdown();
receiverThread.interrupt();
objectInput.close();
objectOutput.close();
socket.close();
}
}
\ No newline at end of file
package edu.caltech.nanodb.client;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.ClosedByInterruptException;
import java.util.concurrent.Semaphore;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.commands.Command;
import edu.caltech.nanodb.queryeval.PrettyTuplePrinter;
import edu.caltech.nanodb.relations.Schema;
import edu.caltech.nanodb.relations.Tuple;
import edu.caltech.nanodb.server.CommandResult;
import edu.caltech.nanodb.server.CommandState;
import edu.caltech.nanodb.server.SharedServer;
/**
* This class implements a client the can connect to the NanoDB
* {@link edu.caltech.nanodb.server.SharedServer shared server} and
* send/receive commands and data.
*/
public class SharedServerClient extends InteractiveClient {
private static Logger logger = LogManager.getLogger(SharedServerClient.class);
/** The socket used to communicate with the shared server. */
private Socket socket;
/**
* This stream is used to receive objects (tuples, messages, etc.) from
* the server. */
private ObjectInputStream objectInput;
/**
* This stream is used to send objects (commands, specifically) to the
* server.
*/
private ObjectOutputStream objectOutput;
/**
* This object receives data from the server asynchronously,
* and prints out whatever it receives. It is wrapped by the
* {@link #receiverThread}.
*/
private Receiver receiver;
/**
* This is the thread that the {@link #receiver} object runs within.
*/
private Thread receiverThread;
/**
* This semaphore is used to coordinate when a command has been sent to
* the server, and when the server is finished sending results back to
* the client, so that another command cannot be sent until the current
* one is finished.
*/
private Semaphore semCommandDone;
/**
* This helper class prints out the results that come back from the
* server. It is intended to run within a separate thread.
*/
private class Receiver implements Runnable {
/** The print-stream to output server results on. */
private PrintStream out;
/** A flag indicating when the receiver thread should shut down. */
private boolean done;
public Receiver(PrintStream out) {
this.out = out;
}
public void run() {
PrettyTuplePrinter tuplePrinter = null;
done = false;
while (true) {
try {
Object obj = objectInput.readObject();
if (obj instanceof String) {
// Just print strings to the console
System.out.print(obj);
}
else if (obj instanceof Schema) {
tuplePrinter = new PrettyTuplePrinter(out);
tuplePrinter.setSchema((Schema) obj);
}
else if (obj instanceof Tuple) {
tuplePrinter.process((Tuple) obj);
}
else if (obj instanceof Throwable) {
Throwable t = (Throwable) obj;
t.printStackTrace(System.out);
}
else if (obj instanceof CommandResult) {
CommandResult result = (CommandResult) obj;
if (result.isExit())
done = true;
}
else if (obj instanceof CommandState) {
CommandState state = (CommandState) obj;
if (state == CommandState.COMMAND_COMPLETED) {
if (tuplePrinter != null) {
tuplePrinter.finish();
tuplePrinter = null;
}
// Signal that the command is completed.
semCommandDone.release();
}
}
else {
// TODO: Try to print whatever came across the wire.
System.out.println(obj);
}
}
catch (EOFException e) {
System.out.println("Connection was closed by the server.");
break;
}
catch (SocketException e) {
System.out.println("Socket communication error.");
break;
}
catch (ClosedByInterruptException e) {
System.out.println("Thread was interrupted during an IO operation.");
break;
}
catch (Exception e) {
System.out.println("Exception occurred:");
e.printStackTrace(System.out);
}
}
}
public void shutdown() {
// TODO: Probably need to interrupt the thread. This is pretty
// insufficient, particularly for long-running queries.
done = true;
}
}
public SharedServerClient(String hostname, int port) throws IOException {
// Try to establish a connection to the shared database server.
socket = new Socket(hostname, port);
objectOutput = new ObjectOutputStream(socket.getOutputStream());
objectInput = new ObjectInputStream(socket.getInputStream());
semCommandDone = new Semaphore(0);
}
public void startup() {
// Start up the receiver thread that will print out whatever comes
// across the wire.
receiver = new Receiver(System.out);
receiverThread = new Thread(receiver);
receiverThread.start();
}
public CommandResult handleCommand(String command) {
try {
objectOutput.writeObject(command);
}
catch (IOException e) {
throw new RuntimeException(
"Unexpected error while transmitting command", e);
}
// Wait for the command to be completed.
try {
semCommandDone.acquire();
}
catch (InterruptedException e) {
throw new RuntimeException(
"Interrupted while waiting for command to finish", e);
}
return null;
}
public void shutdown() throws IOException {
receiver.shutdown();
receiverThread.interrupt();
objectInput.close();
objectOutput.close();
socket.close();
}
}
<html>
<body>
This package contains classes that allow NanoDB to be used from multiple
concurrent clients, to maintain client session state, and so forth. Note that
this does not include important details such as transaction isolation or other
concurrency-control features; it is simply the mechanisms to support multiple
clients interacting with the database.
</body>
</html>
package edu.caltech.nanodb.commands;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
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>ANALYZE</tt> SQL command, which
* analyzes a table's internal data and updates its cached statistics to be as
* up-to-date as possible. This is not a standard SQL command, but virtually
* every good database has a mechanism to manually perform this task.
*/
public class AnalyzeCommand 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;
private boolean verbose = false;
/**
* Construct a new <tt>ANALYZE</tt> command with an empty table list.
* Tables can be added to the internal list using the {@link #addTable}
* method.
*
* @param verbose a flag indicating whether this command should produce
* verbose output
*/
public AnalyzeCommand(boolean verbose) {
super(Command.Type.UTILITY);
tableNames = new LinkedHashSet<>();
this.verbose = verbose;
}
/**
* Construct a new <tt>ANALYZE</tt> command with an empty table list.
* Tables can be added to the internal list using the {@link #addTable}
* method.
*/
public AnalyzeCommand() {
this(false);
}
/**
* Construct a new <tt>ANALYZE</tt> command to analyze the specified table.
*
* @param tableName the name of the table to analyze.
*/
public AnalyzeCommand(String tableName) {
this(tableName, false);
}
/**
* Construct a new <tt>ANALYZE</tt> command to analyze the specified table.
*
* @param tableName the name of the table to analyze.
* @param verbose a flag indicating whether this command should produce
* verbose output
*/
public AnalyzeCommand(String tableName, boolean verbose) {
this(verbose);
addTable(tableName);
}
/**
* Add a table to the list of tables to analyze.
*
* @param tableName the name of the table to analyze.
*/
public void addTable(String tableName) {
if (tableName == null)
throw new NullPointerException("tableName cannot be null");
tableNames.add(tableName);
}
/**
* Returns the set of tables to analyze in an unmodifiable set.
*
* @return the set of tables to analyze in an unmodifiable set.
*/
public Set<String> getTableNames() {
return Collections.unmodifiableSet(tableNames);
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
// Make sure that all the tables are valid.
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
ArrayList<TableInfo> tableInfos = new ArrayList<>();
for (String table : tableNames) {
TableInfo tableInfo = tableManager.openTable(table);
tableInfos.add(tableInfo);
}
// Now, analyze each table.
for (TableInfo tableInfo : tableInfos) {
out.println("Analyzing table " + tableInfo.getTableName());
tableManager.analyzeTable(tableInfo);
if (verbose) {
// TODO: Implement
out.println("TODO: Add verbose output... :-P");
}
}
out.println("Analysis complete.");
}
/**
* Prints a simple representation of the analyze command, including the
* names of the tables to be analyzed.
*
* @return a string representing this analyze command
*/
@Override
public String toString() {
return "Analyze[" + tableNames + "]";
}
}
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 starts a transaction, such as
* <tt>BEGIN</tt>, <tt>BEGIN WORK</tt>, or <tt>START TRANSACTION</tt>.
*/
public class BeginTransactionCommand extends Command {
public BeginTransactionCommand() {
super(Type.UTILITY);
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
// Begin a transaction.
try {
// Pass true for the "user-started transaction" flag, since the
// user issued the command to do it!
StorageManager storageManager = server.getStorageManager();
storageManager.getTransactionManager().startTransaction(true);
}
catch (TransactionException e) {
throw new ExecutionException(e);
}
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.server.SessionState;
import edu.caltech.nanodb.server.NanoDBServer;
import java.io.PrintStream;
/**
* Abstract base-class for all commands that NanoDB supports. Command classes
* contain both the arguments and configuration details for the command being
* executed, as well as the code for actually performing the command. Databases
* tend to have large <tt>switch</tt> statements controlling how various
* commands are handled, and this really isn't a very pretty way to do things.
* So, NanoDB uses a class-hierarchy for command representation and execution.
* <p>
* The command class is subclassed into various command categories that relate
* to various operations in the database. For example, the {@link QueryCommand}
* class represents all <tt>SELECT</tt>, <tt>INSERT</tt>, <tt>UPDATE</tt>, and
* <tt>DELETE</tt> operations.
*/
public abstract class Command {
/**
* Commands are either Data-Definition Language (DDL), Data-Manipulation
* Language (DML), or utility commands.
*/
public enum Type {
/** A Data Definition Language (DDL) command. */
DDL,
/** A Data Manipulation Language (DML) command. */
DML,
/** A utility command. */
UTILITY
}
/** The type of this command. */
private Type cmdType;
/**
* This is the output stream for the current session, so that command
* output goes to the appropriate client.
*/
protected PrintStream out;
/**
* Create a new command instance, of the specified command-type. The
* constructor is protected, but that is redundant with the fact that the
* class is abstract anyways, so this class cannot be constructed directly.
*
* @param cmdType the general category of command
*/
protected Command(Type cmdType) {
this.cmdType = cmdType;
this.out = SessionState.get().getOutputStream();
}
/** Returns the general type or category of this command. */
public Type getCommandType() {
return cmdType;
}
/**
* Actually performs the command.
*
* @throws ExecutionException if an issue occurs during command execution
*/
public abstract void execute(NanoDBServer server)
throws ExecutionException;
}
package edu.caltech.nanodb.commands;
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
import edu.caltech.nanodb.expressions.TypeConverter;
/**
* This class holds properties that might contain additional details for a
* command. For example, the <tt>CREATE TABLE</tt> DDL command takes
* properties to specify non-default storage engine types, the page size to
* use for the table file, and so forth.
*/
public class CommandProperties {
/** A map of name-value pairs that represent the actual properties. */
private HashMap<String, Object> values = new HashMap<>();
public void set(String name, Object value) {
if (name == null)
throw new IllegalArgumentException("name cannot be null");
if (value == null)
throw new IllegalArgumentException("value cannot be null");
values.put(name, value);
}
public Object get(String name, Object defaultValue) {
Object value = values.get(name);
if (value == null)
value = defaultValue;
return value;
}
public Object get(String name) {
return get(name, null);
}
public int getInt(String name, int defaultValue) {
Object obj = get(name);
if (obj == null)
return defaultValue;
Integer intObj = TypeConverter.getIntegerValue(obj);
return intObj.intValue();
}
public String getString(String name, String defaultValue) {
Object obj = get(name);
if (obj == null)
return defaultValue;
return obj.toString();
}
public Set<String> getNames() {
return Collections.unmodifiableSet(values.keySet());
}
@Override
public String toString() {
return "CommandProperties[" + values.toString() + "]";
}
}
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 commits a transaction, such as
* <tt>COMMIT</tt> or <tt>COMMIT WORK</tt>.
*/
public class CommitTransactionCommand extends Command {
public CommitTransactionCommand() {
super(Type.UTILITY);
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
// Commit the transaction.
try {
StorageManager storageManager = server.getStorageManager();
storageManager.getTransactionManager().commitTransaction();
}
catch (TransactionException e) {
throw new ExecutionException(e);
}
}
}
package edu.caltech.nanodb.commands;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import edu.caltech.nanodb.relations.ForeignKeyValueChangeOption;
import edu.caltech.nanodb.relations.TableConstraintType;
/**
* Constraints may be specified at the table level, or they may be specified
* on individual columns. Obviously, the kinds of constraint allowed depends
* on what the constraint is associated with.
*/
public class ConstraintDecl {
/**
* The optional name of the constraint, or {@code null} if no name was
* specified.
*/
private String name = null;
/** The type of the constraint. */
private TableConstraintType type;
/**
* Flag indicating whether the constraint is specified at the table-level
* or at the column-level. A value of {@code true} indicates that it is a
* column-level constraint; a value of {@code false} indicates that it is
* a table-level constraint.
*/
private boolean columnConstraint;
/**
* For {@link TableConstraintType#UNIQUE} and
* {@link TableConstraintType#PRIMARY_KEY} constraints, this is a list of
* one or more column names that are constrained. Note that for a column
* constraint, this list will contain <i>exactly</i> one column name.
* For a table-level constraint, this list will contain one or more column
* names.
* <p>
* For any other constraint type, this will be set to <tt>null</tt>.
*/
private List<String> columnNames = new ArrayList<>();
/**
* For {@link TableConstraintType#FOREIGN_KEY} constraints, this is the
* table that is referenced by the column.
* <p>
* For any other constraint type, this will be set to <tt>null</tt>.
*/
private String refTableName = null;
/**
* For {@link TableConstraintType#FOREIGN_KEY} constraints, this is a list
* of one or more column names in the reference-table that are referenced
* by the foreign-key constraint. Note that for a column-constraint, this
* list will contain <i>exactly</i> one column-name. For a
* table-constraint, this list will contain one or more column-names.
* <p>
* For any other constraint type, this will be set to <code>null</code>.
*/
private List<String> refColumnNames = new ArrayList<>();
/**
* For {@link TableConstraintType#FOREIGN_KEY} constraints, this is the
* {@link edu.caltech.nanodb.relations.ForeignKeyValueChangeOption} for
* <tt>ON DELETE</tt> behavior.
* <p>
* For any other constraint type, this will be set to <tt>null</tt>.
*/
private ForeignKeyValueChangeOption onDeleteOption = null;
/**
* For {@link TableConstraintType#FOREIGN_KEY} constraints, this is the
* {@link edu.caltech.nanodb.relations.ForeignKeyValueChangeOption} for
* <tt>ON UPDATE</tt> behavior.
* <p>
* For any other constraint type, this will be set to <tt>null</tt>.
*/
private ForeignKeyValueChangeOption onUpdateOption = null;
/** Create a new unnamed constraint for a table or a table-column. */
public ConstraintDecl(TableConstraintType type, boolean columnConstraint) {
this(type, null, columnConstraint);
}
/** Create a new named constraint for a table or a table-column. */
public ConstraintDecl(TableConstraintType type, String name,
boolean columnConstraint) {
this.type = type;
this.name = name;
this.columnConstraint = columnConstraint;
}
/**
* Returns the name of the constraint.
* @return the name of the constraint.
*/
public String getName() {
return name;
}
/** Returns the type of this constraint. */
public TableConstraintType getType() {
return type;
}
/**
* Returns <tt>true</tt> if this constraint is associated with a
* table-column, or <tt>false</tt> if it is a table-level constraint.
*/
public boolean isColumnConstraint() {
return columnConstraint;
}
/**
* Add a column to the constraint. This specifies that the constraint
* governs values in the column. For column-level constraints, only a
* single column may be specified. For table-level constraints, one or more
* columns may be specified.
*
* @param columnName the column governed by the constraint
*
* @throws NullPointerException if columnName is <tt>null</tt>
* @throws IllegalStateException if this is a column-constraint and
* there is already one column specified
*
* @design (donnie) Column names are checked for existence and uniqueness
* when initializing the corresponding objects for storage on the
* table schema. See
* {@link edu.caltech.nanodb.relations.Schema#addCandidateKey} and
* {@link edu.caltech.nanodb.relations.Schema#addForeignKey}
* for details.
*/
public void addColumn(String columnName) {
if (columnName == null)
throw new NullPointerException("columnName");
if (columnNames.size() == 1 && isColumnConstraint()) {
throw new IllegalStateException(
"Cannot specify multiple columns in a column-constraint.");
}
columnNames.add(columnName);
}
public List<String> getColumnNames() {
return Collections.unmodifiableList(columnNames);
}
/**
* Add a reference-table to a {@link TableConstraintType#FOREIGN_KEY}
* constraint. This specifies the table that constrains the values in
* the column.
*
* @param tableName the table referenced in the constraint
*
* @throws NullPointerException if tableName is <tt>null</tt>
* @throws IllegalStateException if this constraint is not a foreign-key
* constraint
*
* @design (donnie) Existence of the referenced table is checked in the
* {@link CreateTableCommand#execute} method's operation.
*/
public void setRefTable(String tableName) {
if (type != TableConstraintType.FOREIGN_KEY) {
throw new IllegalStateException(
"Reference tables are only specified on FOREIGN_KEY constraints.");
}
if (tableName == null)
throw new IllegalArgumentException("tableName must be specified");
refTableName = tableName;
}
/**
* Returns the name of the referenced table for a
* {@link TableConstraintType#FOREIGN_KEY} constraint.
*
* @return the name of the referenced table for a FOREIGN_KEY constraint.
*/
public String getRefTable() {
if (type != TableConstraintType.FOREIGN_KEY) {
throw new IllegalStateException(
"Reference tables are only specified on FOREIGN_KEY constraints.");
}
return refTableName;
}
/**
* Add a reference-column to a {@link TableConstraintType#FOREIGN_KEY}
* constraint. This specifies the column that constrains the values in
* the column. For column-level constraints, only a single column may be
* specified. For table-level constraints, one or more columns may be
* specified.
*
* @param columnName the column referenced in the constraint
*
* @throws NullPointerException if columnName is <tt>null</tt>
* @throws IllegalStateException if this constraint is not a foreign-key
* constraint, or if this is a column-constraint and there is
* already one reference-column specified
*
* @design (donnie) Column names are checked for existence and uniqueness
* when initializing the corresponding objects for storage on the
* table schema. See
* {@link edu.caltech.nanodb.relations.Schema#addCandidateKey} and
* {@link edu.caltech.nanodb.relations.Schema#addForeignKey}
* for details.
*/
public void addRefColumn(String columnName) {
if (type != TableConstraintType.FOREIGN_KEY) {
throw new IllegalStateException(
"Reference columns only allowed on FOREIGN_KEY constraints.");
}
if (columnName == null)
throw new NullPointerException("columnName");
if (refColumnNames.size() == 1 && isColumnConstraint()) {
throw new IllegalStateException("Cannot specify multiple " +
"reference-columns in a column-constraint.");
}
refColumnNames.add(columnName);
}
public List<String> getRefColumnNames() {
return Collections.unmodifiableList(refColumnNames);
}
/**
* Add an <tt>ON DELETE</tt> option to a
* {@link TableConstraintType#FOREIGN_KEY} constraint.
*
* @param f the {@link ForeignKeyValueChangeOption} to set for
* <tt>ON DELETE</tt> behavior
*
* @throws IllegalStateException if this constraint is not a foreign-key
* constraint
*/
public void setOnDeleteOption(ForeignKeyValueChangeOption f) {
if (type != TableConstraintType.FOREIGN_KEY) {
throw new IllegalStateException(
"ON DELETE only specified on FOREIGN_KEY constraints.");
}
onDeleteOption = f;
}
/**
* Returns the <tt>ON DELETE</tt> {@link ForeignKeyValueChangeOption} for
* a {@link TableConstraintType#FOREIGN_KEY} constraint.
*
* @return the <tt>ON DELETE</tt> {@link ForeignKeyValueChangeOption}
*/
public ForeignKeyValueChangeOption getOnDeleteOption() {
if (type != TableConstraintType.FOREIGN_KEY) {
throw new IllegalStateException(
"ON DELETE only specified on FOREIGN_KEY constraints.");
}
return onDeleteOption;
}
/**
* Add an <tt>ON UPDATE</tt> option to a
* {@link TableConstraintType#FOREIGN_KEY} constraint.
*
* @param f the {@link ForeignKeyValueChangeOption} to set for
* <tt>ON UPDATE</tt> behavior
*
* @throws IllegalStateException if this constraint is not a foreign-key
* constraint
*/
public void setOnUpdateOption(ForeignKeyValueChangeOption f) {
if (type != TableConstraintType.FOREIGN_KEY) {
throw new IllegalStateException(
"ON UPDATE only specified on FOREIGN_KEY constraints.");
}
onUpdateOption = f;
}
/**
* Returns the <tt>ON UPDATE</tt> {@link ForeignKeyValueChangeOption} for
* a {@link TableConstraintType#FOREIGN_KEY} constraint.
*
* @return the <tt>ON UPDATE</tt> {@link ForeignKeyValueChangeOption}
*/
public ForeignKeyValueChangeOption getOnUpdateOption() {
if (type != TableConstraintType.FOREIGN_KEY) {
throw new IllegalStateException(
"ON UPDATE only specified on FOREIGN_KEY constraints.");
}
return onUpdateOption;
}
@Override
public String toString() {
return "Constraint[" + (name != null ? name : "(unnamed)") + " : " +
type + ", " + (isColumnConstraint() ? "column" : "table") + "]";
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.server.NanoDBServer;
/**
* This command "crashes" the database by shutting it down immediately without
* any proper cleanup or flushing of caches.
*/
public class CrashCommand extends Command {
/**
* Stores how many seconds to wait before crashing the database. A value
* of 0 means "crash immediately."
*/
private int secondsToCrash;
/**
* Construct a new <tt>CRASH</tt> command that will wait for the specified
* number of seconds and then crash the database.
*/
public CrashCommand(int secs) {
super(Command.Type.UTILITY);
secondsToCrash = secs;
}
/**
* Construct a new <tt>CRASH</tt> command that will crash the database
* immediately.
*/
public CrashCommand() {
this(0);
}
public int getSecondsToCrash() {
return secondsToCrash;
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
if (secondsToCrash <= 0) {
// Crash immediately.
doCrash();
}
else {
// Wait for the specified amount of time in a thread, then crash.
Thread t = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(secondsToCrash * 1000);
out.println("\n");
doCrash();
} catch (InterruptedException e) {
out.println("Crash-thread was interrupted, not crashing.");
}
}
});
t.start();
}
}
private void doCrash() {
out.println("Goodbye, cruel world! I'm taking your data with me!!!");
// Using this API call avoids running shutdown hooks, finalizers, etc.
// 22 is the exit status of the VM's process
Runtime.getRuntime().halt(22);
}
/**
* Prints a simple representation of the crash command.
*
* @return a string representing this crash command
*/
@Override
public String toString() {
String s = "Crash";
if (secondsToCrash > 0)
s += "[wait " + secondsToCrash + " seconds]";
return s;
}
}
package edu.caltech.nanodb.commands;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.indexes.IndexManager;
import edu.caltech.nanodb.relations.ColumnRefs;
import edu.caltech.nanodb.relations.IndexColumnRefs;
import edu.caltech.nanodb.relations.KeyColumnRefs;
import edu.caltech.nanodb.relations.TableConstraintType;
import edu.caltech.nanodb.relations.TableInfo;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.storage.TableManager;
import edu.caltech.nanodb.storage.StorageManager;
/** This command-class represents the <tt>CREATE INDEX</tt> DDL command. */
public class CreateIndexCommand extends Command {
/** A logging object for reporting anything interesting that happens. **/
private static Logger logger = LogManager.getLogger(CreateIndexCommand.class);
/** The name of the index being created. */
private String indexName;
/**
* This flag specifies whether the index is a unique index or not. If the
* value is true then no key-value may appear multiple times; if the value
* is false then a key-value may appear multiple times.
*/
private boolean unique;
/**
* If this flag is {@code true} then the create-index operation should
* only be performed if the specified index doesn't already exist.
*/
private boolean ifNotExists;
/** The name of the table that the index is built against. */
private String tableName;
/**
* The list of column-names that the index is built against. The order of
* these values is important; for ordered indexes, the index records must be
* kept in the order specified by the sequence of column names.
*/
private ArrayList<String> columnNames = new ArrayList<>();
/** Any additional properties specified in the command. */
private CommandProperties properties;
public CreateIndexCommand(String indexName, String tableName,
boolean unique) {
super(Type.DDL);
if (tableName == null)
throw new IllegalArgumentException("tableName cannot be null");
this.indexName = indexName;
this.tableName = tableName;
this.unique = unique;
}
/**
* Returns {@code true} if index creation should only be attempted if it
* doesn't already exist, {@code false} if index creation should always
* be attempted.
*
* @return {@code true} if index creation should only be attempted if it
* doesn't already exist, {@code false} if index creation should
* always be attempted.
*/
public boolean getIfNotExists() {
return ifNotExists;
}
/**
* Sets the flag indicating whether index creation should only be
* attempted if it doesn't already exist.
*
* @param b the flag indicating whether index creation should only be
* attempted if it doesn't already exist.
*/
public void setIfNotExists(boolean b) {
ifNotExists = b;
}
/**
* 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 list of column names specified for the index as an
* unmodifiable list.
*
* @return the list of column names specified for the index as an
* unmodifiable list.
*/
public List<String> getColumnNames() {
return Collections.unmodifiableList(columnNames);
}
/**
* Returns true if the requested index is a unique index; false otherwise.
*
* @return true if the requested index is a unique index; false otherwise.
*/
public boolean isUnique() {
return unique;
}
/**
* Sets any additional properties associated with the command. The
* value may be {@code null} to indicate no properties.
*
* @param properties any additional properties to associate with the
* command.
*/
public void setProperties(CommandProperties properties) {
this.properties = properties;
}
/**
* Returns any additional properties specified for the command, or
* {@code null} if no additional properties were specified.
*
* @return any additional properties specified for the command, or
* {@code null} if no additional properties were specified.
*/
public CommandProperties getProperties() {
return properties;
}
/**
* Adds a column to the list of columns to be included in the index.
*
* @param columnName the name of the column to add to the columns to be
* included in the index.
*/
public void addColumn(String columnName) {
if (columnName == null)
throw new IllegalArgumentException("columnName cannot be null");
this.columnNames.add(columnName);
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
IndexManager indexManager = storageManager.getIndexManager();
// Open the table and get the schema for the table.
logger.debug(String.format("Opening table %s to retrieve schema",
tableName));
TableInfo tableInfo = tableManager.openTable(tableName);
int[] cols = tableInfo.getSchema().getColumnIndexes(columnNames);
IndexColumnRefs index = new IndexColumnRefs(indexName, cols);
if (unique) {
// Also record a candidate key on the columns.
KeyColumnRefs key = new KeyColumnRefs(cols, indexName, TableConstraintType.UNIQUE);
}
indexManager.addIndexToTable(tableInfo, index);
logger.debug(String.format("New index %s on table %s is created!",
indexName, tableName));
out.printf("Created index %s on table %s.%n", indexName, tableName);
}
}
package edu.caltech.nanodb.commands;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.indexes.IndexManager;
import edu.caltech.nanodb.relations.ColumnInfo;
import edu.caltech.nanodb.relations.ForeignKeyColumnRefs;
import edu.caltech.nanodb.relations.IndexColumnRefs;
import edu.caltech.nanodb.relations.KeyColumnRefs;
import edu.caltech.nanodb.relations.Schema;
import edu.caltech.nanodb.relations.TableConstraintType;
import edu.caltech.nanodb.relations.TableInfo;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.server.properties.PropertyRegistry;
import edu.caltech.nanodb.storage.TableManager;
import edu.caltech.nanodb.storage.StorageManager;
/**
* This command handles the <tt>CREATE TABLE</tt> DDL operation.
*/
public class CreateTableCommand extends Command {
/** A logging object for reporting anything interesting that happens. */
private static Logger logger = LogManager.getLogger(CreateTableCommand.class);
/** Name of the table to be created. */
private String tableName;
/** If this flag is {@code true} then the table is a temporary table. */
private boolean temporary;
/**
* If this flag is {@code true} then the create-table operation should
* only be performed if the specified table doesn't already exist.
*/
private boolean ifNotExists;
/** List of column-declarations for the new table. */
private List<ColumnInfo> columnInfos = new ArrayList<>();
/** List of constraints for the new table. */
private List<ConstraintDecl> constraints = new ArrayList<>();
/** Any additional properties specified in the command. */
private CommandProperties properties;
/**
* Create a new object representing a <tt>CREATE TABLE</tt> statement.
*
* @param tableName the name of the table to be created
*/
public CreateTableCommand(String tableName) {
super(Command.Type.DDL);
if (tableName == null)
throw new IllegalArgumentException("tableName cannot be null");
this.tableName = tableName;
}
/**
* Returns {@code true} if the table is a temporary table, {@code false}
* otherwise.
*
* @return {@code true} if the table is a temporary table, {@code false}
* otherwise.
*/
public boolean isTemporary() {
return temporary;
}
/**
* Specifies whether the table is a temporary table or not.
*
* @param b {@code true} if the table is a temporary table, {@code false}
* otherwise.
*/
public void setTemporary(boolean b) {
temporary = b;
}
/**
* Returns {@code true} if table creation should only be attempted if it
* doesn't already exist, {@code false} if table creation should always
* be attempted.
*
* @return {@code true} if table creation should only be attempted if it
* doesn't already exist, {@code false} if table creation should
* always be attempted.
*/
public boolean getIfNotExists() {
return ifNotExists;
}
/**
* Sets the flag indicating whether table creation should only be
* attempted if it doesn't already exist.
*
* @param b the flag indicating whether table creation should only be
* attempted if it doesn't already exist.
*/
public void setIfNotExists(boolean b) {
ifNotExists = b;
}
/**
* Sets any additional properties associated with the command. The
* value may be {@code null} to indicate no properties.
*
* @param properties any additional properties to associate with the
* command.
*/
public void setProperties(CommandProperties properties) {
this.properties = properties;
}
/**
* Returns any additional properties specified for the command, or
* {@code null} if no additional properties were specified.
*
* @return any additional properties specified for the command, or
* {@code null} if no additional properties were specified.
*/
public CommandProperties getProperties() {
return properties;
}
/**
* Adds a column description to this create-table command. This method is
* primarily used by the SQL parser.
*
* @param colDecl the details of the column to add
*
* @throws NullPointerException if colDecl is null
*/
public void addColumn(TableColumnDecl colDecl) {
if (colDecl == null)
throw new IllegalArgumentException("colDecl cannot be null");
// Pull out the column-info value first, and add it to the table
// specification. If the table-name doesn't match, update this.
ColumnInfo colInfo = colDecl.getColumnInfo();
if (!tableName.equals(colInfo.getTableName())) {
colInfo = new ColumnInfo(colInfo.getName(), tableName,
colInfo.getType());
}
columnInfos.add(colInfo);
// Next, if any column-level constraints are specified,
// add them to the collection of constraints.
for (ConstraintDecl constraint : colDecl.getConstraints())
addConstraint(constraint);
}
/**
* Returns an immutable list of the column descriptions that are part of
* this <tt>CREATE TABLE</tt> command.
*
* @return an immutable list of the column descriptions that are part of
* this <tt>CREATE TABLE</tt> command.
*/
public List<ColumnInfo> getColumns() {
return Collections.unmodifiableList(columnInfos);
}
/**
* Adds a constraint to this create-table command. This method is primarily
* used by the SQL parser.
*
* @param con the details of the table constraint to add
*
* @throws NullPointerException if con is null
*/
public void addConstraint(ConstraintDecl con) {
if (con == null)
throw new IllegalArgumentException("con cannot be null");
constraints.add(con);
}
/**
* Returns an immutable list of the constraint declarations that are part
* of this <tt>CREATE TABLE</tt> command.
*
* @return an immutable list of the constraint declarations that are part
* of this <tt>CREATE TABLE</tt> command.
*/
public List<ConstraintDecl> getConstraints() {
return Collections.unmodifiableList(constraints);
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
PropertyRegistry propReg = server.getPropertyRegistry();
boolean createIndexesOnKeys = propReg.getBooleanProperty(
NanoDBServer.PROP_CREATE_INDEXES_ON_KEYS);
// See if the table already exists.
if (ifNotExists && tableManager.tableExists(tableName)) {
out.printf("Table %s already exists; skipping create-table.%n",
tableName);
return;
}
// Set up the table's schema based on the command details.
logger.debug("Creating a TableSchema object for the new table " +
tableName + ".");
Schema schema = new Schema();
for (ColumnInfo colInfo : columnInfos) {
try {
schema.addColumnInfo(colInfo);
}
catch (IllegalArgumentException iae) {
throw new ExecutionException("Duplicate or invalid column \"" +
colInfo.getName() + "\".", iae);
}
}
// Do some basic verification of the table constraints:
// * Verify that all named constraints are uniquely named.
// * Open all tables referenced by foreign-key constraints, to ensure
// they exist. (More verification will occur later.)
HashSet<String> constraintNames = new HashSet<>();
HashMap<String, TableInfo> referencedTables = new HashMap<>();
for (ConstraintDecl cd: constraints) {
String name = cd.getName();
if (name != null && !constraintNames.add(name)) {
throw new ExecutionException("Constraint name " + name +
" appears multiple times.");
}
if (cd.getType() == TableConstraintType.FOREIGN_KEY) {
String refTableName = cd.getRefTable();
TableInfo refTblInfo = tableManager.openTable(refTableName);
if (refTblInfo == null) {
throw new ExecutionException("Referenced table " +
refTableName + " doesn't exist.");
}
referencedTables.put(refTableName, refTblInfo);
}
}
// Initialize all the constraints on the table.
initTableConstraints(storageManager, schema, referencedTables);
// Create the table.
logger.debug("Creating the new table " + tableName + " on disk.");
TableInfo tableInfo = tableManager.createTable(tableName, schema, properties);
logger.debug("New table " + tableName + " was created.");
if (createIndexesOnKeys) {
logger.debug("Creating indexes on the new table, and any " +
"referenced tables");
initIndexes(storageManager, tableInfo);
}
out.println("Created table: " + tableName);
}
private void initTableConstraints(StorageManager storageManager,
Schema schema, HashMap<String, TableInfo> referencedTables) {
if (constraints.isEmpty()) {
logger.debug("No table constraints specified, our work is done.");
return;
}
TableManager tableManager = storageManager.getTableManager();
logger.debug("Adding " + constraints.size() +
" constraints to the table.");
HashSet<String> constraintNames = new HashSet<>();
for (ConstraintDecl cd : constraints) {
// Make sure that if constraint names are specified, every
// constraint is actually uniquely named.
if (cd.getName() != null) {
if (!constraintNames.add(cd.getName())) {
throw new ExecutionException("Constraint name " +
cd.getName() + " appears multiple times.");
}
}
TableConstraintType type = cd.getType();
if (type == TableConstraintType.PRIMARY_KEY ||
type == TableConstraintType.UNIQUE) {
// Make a candidate-key constraint and put it on the schema.
int[] cols = schema.getColumnIndexes(cd.getColumnNames());
KeyColumnRefs ck = new KeyColumnRefs(cols, cd.getName(), type);
if (type == TableConstraintType.PRIMARY_KEY) {
// Add NOT NULL constraints for all primary-key columns.
for (int iCol : cols)
schema.addNotNull(iCol);
}
schema.addCandidateKey(ck);
}
else if (type == TableConstraintType.FOREIGN_KEY) {
// Make a foreign key constraint and put it on the schema.
// This involves these steps:
// 1) Create the foreign key on this table's schema.
// 2) Update the referenced table's schema to record that
// this table references that table.
// This should never be null since we already resolved all
// foreign-key table references earlier.
TableInfo refTableInfo = referencedTables.get(cd.getRefTable());
Schema refSchema = refTableInfo.getSchema();
// The makeForeignKey() method ensures that the referenced
// columns are also a candidate key (or primary key) on the
// referenced table.
ForeignKeyColumnRefs fk = DDLUtils.makeForeignKey(schema,
refTableInfo, cd);
schema.addForeignKey(fk);
// Update the referenced table's schema to record that this
// table has a foreign-key reference to the table.
if (refSchema.addReferencingTable(tableName))
tableManager.saveTableInfo(refTableInfo);
}
else if (type == TableConstraintType.NOT_NULL) {
int idx = schema.getColumnIndex(cd.getColumnNames().get(0));
schema.addNotNull(idx);
}
else {
throw new ExecutionException("Unexpected constraint type " +
cd.getType());
}
}
}
private void initIndexes(StorageManager storageManager,
TableInfo tableInfo) {
IndexManager indexManager = storageManager.getIndexManager();
Schema schema = tableInfo.getSchema();
for (ConstraintDecl cd : constraints) {
TableConstraintType type = cd.getType();
if (type == TableConstraintType.PRIMARY_KEY ||
type == TableConstraintType.UNIQUE) {
int[] cols = schema.getColumnIndexes(cd.getColumnNames());
IndexColumnRefs ck = new IndexColumnRefs(cd.getName(), cols);
// Make the index. This also updates the table schema with
// the fact that there is another candidate key on the table.
indexManager.addIndexToTable(tableInfo, ck);
}
else if (type == TableConstraintType.FOREIGN_KEY) {
// Check if there is already an index on the foreign-key
// columns. If there is not, we will create a non-unique
// index on those columns.
int[] fkCols = schema.getColumnIndexes(cd.getColumnNames());
IndexColumnRefs fkColRefs = null; // schema.getIndexOnColumns(new ColumnRefs(fkCols));
if (fkColRefs == null) {
// Need to make a new index for this foreign-key reference
fkColRefs = new IndexColumnRefs(cd.getName(), fkCols);
fkColRefs.setConstraintType(TableConstraintType.FOREIGN_KEY);
// Make the index.
indexManager.addIndexToTable(tableInfo, fkColRefs);
logger.debug(String.format(
"Created index %s on table %s to enforce foreign key.",
fkColRefs.getIndexName(), tableInfo.getTableName()));
}
}
}
}
@Override
public String toString() {
return "CreateTable[" + tableName + "]";
}
/**
* Returns a verbose, multi-line string containing all of the details of
* this table.
*
* @return a detailed description of the table described by this command
*/
public String toVerboseString() {
StringBuilder strBuf = new StringBuilder();
strBuf.append(toString());
strBuf.append('\n');
for (ColumnInfo colInfo : columnInfos) {
strBuf.append('\t');
strBuf.append(colInfo.toString());
strBuf.append('\n');
}
for (ConstraintDecl con : constraints) {
strBuf.append('\t');
strBuf.append(con.toString());
strBuf.append('\n');
}
return strBuf.toString();
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.queryast.SelectClause;
import edu.caltech.nanodb.server.NanoDBServer;
/**
* This command-class represents the <tt>CREATE VIEW</tt> DDL command.
*/
public class CreateViewCommand extends Command {
private String viewName;
private SelectClause selectClause;
public CreateViewCommand(String viewName, SelectClause selectClause) {
super(Type.DDL);
if (viewName == null)
throw new IllegalArgumentException("viewName cannot be null");
if (selectClause == null)
throw new IllegalArgumentException("selectClause cannot be null");
this.viewName = viewName;
this.selectClause = selectClause;
}
@Override
public void execute(NanoDBServer server)
throws ExecutionException {
throw new ExecutionException("Not yet implemented!");
}
}
package edu.caltech.nanodb.commands;
import java.util.ArrayList;
import java.util.List;
import edu.caltech.nanodb.relations.ColumnInfo;
import edu.caltech.nanodb.relations.ColumnType;
import edu.caltech.nanodb.relations.ForeignKeyColumnRefs;
import edu.caltech.nanodb.relations.Schema;
import edu.caltech.nanodb.relations.SchemaNameException;
import edu.caltech.nanodb.relations.TableInfo;
/**
* This helper class provides some useful functions for constructing keys and
* indexes and other details for tables that are being initialized.
*/
public class DDLUtils {
/**
* This method constructs a {@link ForeignKeyColumnRefs} object that
* includes the columns named in the input list, as well as the referenced
* table and column names. Note that this method <u>does not</u> update
* the schema stored on disk, or create any other supporting files.
*
* @param tableSchema the schema of the table that the foreign key
* will be added to
*
* @param refTableInfo the table that the foreign key will reference
*
* @param constraintDecl the parsed constraint declaration describing
* the foreign key
*
* @return an object describing the foreign key
*
* @throws SchemaNameException if a column-name cannot be found, or if a
* column-name is ambiguous (unlikely), or if a column is
* specified multiple times in the input list.
*/
public static ForeignKeyColumnRefs makeForeignKey(Schema tableSchema,
TableInfo refTableInfo, ConstraintDecl constraintDecl) {
// if (tableInfo == null)
// throw new IllegalArgumentException("tableInfo must be specified");
if (refTableInfo == null)
throw new IllegalArgumentException("refTableInfo must be specified");
if (constraintDecl == null)
throw new IllegalArgumentException("constraintDecl must be specified");
Schema refTableSchema = refTableInfo.getSchema();
int[] colIndexes = tableSchema.getColumnIndexes(
constraintDecl.getColumnNames());
List<String> refColumnNames = constraintDecl.getRefColumnNames();
int[] refColIndexes;
if (refColumnNames.isEmpty()) {
// The constraint declaration doesn't specify column names because
// it wants to use the primary key of the referenced table.
refColIndexes = refTableSchema.getPrimaryKey().getCols();
}
else {
// The constraint declaration specifies column names, so convert
// those names into the corresponding column indexes.
refColIndexes = refTableSchema.getColumnIndexes(refColumnNames);
}
if (!refTableSchema.hasKeyOnColumns(refColIndexes)) {
throw new SchemaNameException(String.format(
"Referenced columns %s in table %s are not a candidate key",
refColumnNames, refTableInfo.getTableName()));
}
ArrayList<ColumnInfo> colInfos = tableSchema.getColumnInfos(colIndexes);
ArrayList<ColumnInfo> refColInfos = refTableSchema.getColumnInfos(refColIndexes);
// Check if the corresponding columns in the FK are the same types
for (int i = 0; i < colInfos.size(); i++) {
ColumnType type = colInfos.get(i).getType();
ColumnType refType = refColInfos.get(i).getType();
if (!type.equals(refType)) {
throw new IllegalArgumentException("columns in " +
"child and parent tables of the foreign key must be " +
"of the same type!");
}
}
// The onDelete and onUpdate values could be null if they are
// unspecified in the constructor. They are set to
// ForeignKeyValueChangeOption.RESTRICT as a default in this case in
// the constructor for ForeignKeyColumnIndexes.
ForeignKeyColumnRefs fk = new ForeignKeyColumnRefs(colIndexes,
refTableInfo.getTableName(), refColIndexes,
constraintDecl.getOnDeleteOption(),
constraintDecl.getOnUpdateOption());
fk.setConstraintName(constraintDecl.getName());
return fk;
}
}
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