Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
610 changes: 610 additions & 0 deletions zudb-tck/src/main/java/dev/zudb/tck/MisuseTest.java

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion zudb/src/main/java/dev/zudb/Appender.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ public final class Appender implements AutoCloseable {

private final ZuBinding zu;
private final AtomicLong handle;
private final Connection conn;
private long finished = -1;

Appender(ZuBinding zu, long handle) {
Appender(ZuBinding zu, long handle, Connection conn) {
this.zu = zu;
this.handle = new AtomicLong(handle);
this.conn = conn;
}

/**
Expand Down Expand Up @@ -401,6 +403,16 @@ private long open() {
throw new ZuClosedException(
Diagnostic.misuse(Status.MISUSE_CLOSED, "this appender is closed"));
}
// The engine refuses this too, and refuses it without an error record
// attached, so what a caller would otherwise be told is the name of a C
// function. An appender outliving its connection is an ordinary mistake
// in a program that closes things in the wrong order, and the sentence
// that names it is worth more than the one that names us.
if (conn != null && conn.isClosed()) {
throw new ZuClosedException(
Diagnostic.misuse(
Status.MISUSE_CLOSED, "the connection this appender writes through is closed"));
}
return h;
}
}
12 changes: 9 additions & 3 deletions zudb/src/main/java/dev/zudb/Connection.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;

Expand Down Expand Up @@ -56,6 +57,7 @@ public final class Connection implements AutoCloseable {
* @return the connection, which the caller closes
*/
public static Connection open(Path path) {
Objects.requireNonNull(path, "path");
ZuBinding zu = Zu.binding();
return new Connection(zu, zu.open(path.toString()));
}
Expand All @@ -67,7 +69,7 @@ public static Connection open(Path path) {
* @return the connection, which the caller closes
*/
public static Connection open(String path) {
return open(Path.of(path));
return open(Path.of(Objects.requireNonNull(path, "path")));
}

/**
Expand All @@ -78,6 +80,7 @@ public static Connection open(String path) {
* @return the connection, which the caller closes
*/
public static Connection create(Path path) {
Objects.requireNonNull(path, "path");
ZuBinding zu = Zu.binding();
return new Connection(zu, zu.create(path.toString()));
}
Expand All @@ -89,7 +92,7 @@ public static Connection create(Path path) {
* @return the connection, which the caller closes
*/
public static Connection create(String path) {
return create(Path.of(path));
return create(Path.of(Objects.requireNonNull(path, "path")));
}

/**
Expand Down Expand Up @@ -120,6 +123,7 @@ public static Connection memory() {
* @return the result, which the caller closes
*/
public Result query(String statement) {
Objects.requireNonNull(statement, "statement");
return new Result(zu, zu.query(open(), statement), this);
}

Expand All @@ -144,6 +148,7 @@ public void execute(String statement) {
* @return the statement, which the caller closes
*/
public Statement prepare(String statement) {
Objects.requireNonNull(statement, "statement");
return new Statement(zu, zu.prepare(open(), statement), this);
}

Expand All @@ -158,7 +163,8 @@ public Statement prepare(String statement) {
* @return the appender, which the caller closes
*/
public Appender appender(String table) {
return new Appender(zu, zu.appenderOpen(open(), table));
Objects.requireNonNull(table, "table");
return new Appender(zu, zu.appenderOpen(open(), table), this);
}

/**
Expand Down
10 changes: 8 additions & 2 deletions zudb/src/main/java/dev/zudb/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dev.zudb.spi.ZuBinding;
import java.nio.file.Path;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;

/**
Expand Down Expand Up @@ -53,6 +54,8 @@ public static Database open(Path path) {
* @return the database
*/
public static Database open(Path path, Config config) {
Objects.requireNonNull(path, "path");
Objects.requireNonNull(config, "config");
ZuBinding zu = Zu.binding();
return new Database(
zu,
Expand All @@ -68,7 +71,7 @@ public static Database open(Path path, Config config) {
* @return the database
*/
public static Database open(String path) {
return open(Path.of(path), Config.defaults());
return open(Path.of(Objects.requireNonNull(path, "path")), Config.defaults());
}

/**
Expand All @@ -79,7 +82,7 @@ public static Database open(String path) {
* @return the database
*/
public static Database open(String path, Config config) {
return open(Path.of(path), config);
return open(Path.of(Objects.requireNonNull(path, "path")), config);
}

/**
Expand All @@ -105,6 +108,8 @@ public static Database create(Path path) {
* @return the database
*/
public static Database create(Path path, Config config) {
Objects.requireNonNull(path, "path");
Objects.requireNonNull(config, "config");
ZuBinding zu = Zu.binding();
return new Database(
zu,
Expand Down Expand Up @@ -133,6 +138,7 @@ public static Database memory() {
* @return the database
*/
public static Database memory(Config config) {
Objects.requireNonNull(config, "config");
ZuBinding zu = Zu.binding();
return new Database(
zu, zu.databaseMemory(config.memoryLimit(), config.threads(), config.readOnly()));
Expand Down
6 changes: 6 additions & 0 deletions zudb/src/main/java/dev/zudb/Diagnostic.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ public ZuException toException() {
case CONFLICT:
return new ZuTransactionException(this);
case IO:
case CORRUPT:
// A file that is not a database is the same mistake as a file that
// is not there: a path that does not lead to a database. Almost
// every one of these is a caller who mistyped a path or pointed at
// the wrong file, and calling that an internal error tells them to
// file a bug about somebody else's code.
return new ZuConnectionException(this);
default:
return new ZuInternalException(this);
Expand Down
2 changes: 2 additions & 0 deletions zudb/src/main/java/dev/zudb/Loader.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;

/**
Expand Down Expand Up @@ -63,6 +64,7 @@ private Loader(ZuBinding zu, long handle) {
* @throws ZuException if the path exists or cannot be written
*/
public static Loader create(Path path) {
Objects.requireNonNull(path, "path");
ZuBinding zu = Zu.binding();
return new Loader(zu, zu.loaderCreate(path.toString()));
}
Expand Down
52 changes: 52 additions & 0 deletions zudb/src/main/java/dev/zudb/Result.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ public int columnIndex(String name) {
* @return the row, which is good until this result closes
*/
public Row row(long index) {
// A closed result has no rows, so this refuses here rather than handing
// back a Row that fails on the first cell read. The wrong call is this
// one, and a failure two lines further on is a failure a caller has to
// work backwards from.
open();
if (index < 0 || index >= rows) {
throw new ZuProgrammingException(
Diagnostic.misuse(
Expand Down Expand Up @@ -263,6 +268,7 @@ public List<Diagnostic> notices() {
*/
public LongBuffer longs(int column) {
checkColumn(column);
checkHolds("longs", column, Type.INT, Type.BOOL);
LongBuffer b = zu.colLongs(open(), column, rows);
return b == null ? LongBuffer.allocate(0).asReadOnlyBuffer() : b;
}
Expand All @@ -275,6 +281,7 @@ public LongBuffer longs(int column) {
*/
public DoubleBuffer doubles(int column) {
checkColumn(column);
checkHolds("doubles", column, Type.FLOAT, Type.INT);
DoubleBuffer b = zu.colDoubles(open(), column, rows);
return b == null ? DoubleBuffer.allocate(0).asReadOnlyBuffer() : b;
}
Expand All @@ -292,6 +299,7 @@ public DoubleBuffer doubles(int column) {
*/
public LongBuffer nodeOffsets(int column) {
checkColumn(column);
checkHolds("nodeOffsets", column, Type.NODE);
LongBuffer b = zu.colNodeOffsets(open(), column, rows);
return b == null ? LongBuffer.allocate(0).asReadOnlyBuffer() : b;
}
Expand Down Expand Up @@ -464,6 +472,50 @@ long open() {
return h;
}

/**
* Refuses a borrowed column the accessor cannot read, in this client's own
* words.
*
* <p>The engine refuses it too, but a call that comes back without a
* {@code zu_error} on it leaves nothing to say except which C function was
* called, and "zu_result_col_i64 answered MISUSE" is a sentence about our
* implementation rather than about the caller's program. Here there is a
* column name and a type to name, so this says them.
*
* <p>The first row is what is read, because a borrowed column is one lane
* of one type and the first cell is the cheapest place that says which. A
* null there says nothing, so it passes and the engine has the last word.
*/
private void checkHolds(String accessor, int column, Type... reads) {
if (rows == 0) {
return;
}
Type holds = cellType(0, column);
if (holds == Type.NULL) {
return;
}
for (Type ok : reads) {
if (holds == ok) {
return;
}
}
throw new ZuProgrammingException(
Diagnostic.misuse(
Status.MISUSE,
"column "
+ column
+ " of this result is "
+ names.get(column)
+ ", which holds "
+ holds
+ ", and "
+ accessor
+ "("
+ column
+ ") reads a column of "
+ reads[0]));
}

void checkColumn(int column) {
if (column < 0 || column >= columns) {
throw new ZuProgrammingException(
Expand Down
7 changes: 7 additions & 0 deletions zudb/src/main/java/dev/zudb/ZuConnectionException.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
* <p>Nothing here is about the statement. A path that is not a zu database, a
* file the process may not open, a disk that answered an error: the text was
* never the problem and rewriting it will not help.
*
* <p>A file whose contents are not a database is here rather than in
* {@link ZuInternalException}, and the two readings of that were weighed. A
* header that says something impossible could be a database this process
* corrupted, and it could be a JPEG somebody pointed at. The second is what
* almost every one of these is, so this is the class that sends a caller to
* look at the path they passed rather than to open a bug.
*/
public class ZuConnectionException extends ZuException {

Expand Down
6 changes: 5 additions & 1 deletion zudb/src/test/java/dev/zudb/DiagnosticTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ void aStatusWithNoCodePicksTheException() {
ZuInterruptedException.class, diagnostic(Status.INTERRUPTED, null).toException());
assertInstanceOf(ZuTransactionException.class, diagnostic(Status.CONFLICT, null).toException());
assertInstanceOf(ZuConnectionException.class, diagnostic(Status.IO, null).toException());
assertInstanceOf(ZuInternalException.class, diagnostic(Status.CORRUPT, null).toException());
// A file that is not a database is a path mistake, not a bug in the
// engine, so it lands beside the file that is not there rather than in
// the class that asks the caller to report it.
assertInstanceOf(ZuConnectionException.class, diagnostic(Status.CORRUPT, null).toException());
assertInstanceOf(ZuInternalException.class, diagnostic(Status.UNKNOWN, null).toException());
}

@Test
Expand Down
Loading