package com.g414.haildb;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Map;
import com.g414.haildb.impl.jna.HailDB;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.PointerByReference;
public class Cursor {
public enum CursorDirection {
ASC, DESC;
}
public enum SearchMode {
/* see InnoDB.ib_srch_mode_t.IB_CUR_G */
G(HailDB.ib_srch_mode_t.IB_CUR_G), GE(HailDB.ib_srch_mode_t.IB_CUR_GE), L(
HailDB.ib_srch_mode_t.IB_CUR_L), LE(
HailDB.ib_srch_mode_t.IB_CUR_LE);
private final int code;
private SearchMode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public static SearchMode fromCode(int code) {
return SearchMode.values()[code - 1];
}
}
public enum MatchMode {
/* see InnoDB.ib_match_mode_t */
CLOSEST(HailDB.ib_match_mode_t.IB_CLOSEST_MATCH), EXACT(
HailDB.ib_match_mode_t.IB_EXACT_MATCH), EXACT_PREFIX(
HailDB.ib_match_mode_t.IB_EXACT_PREFIX);
private final int code;
private MatchMode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public static MatchMode fromCode(int code) {
return MatchMode.values()[code];
}
}
public enum SearchResultCode {
/* returns -1, 0 or 1 based on search result */
BEFORE(0), EQUALS(1), AFTER(2);
private final int code;
private SearchResultCode(int code) {
this.code = code;
}
public int getCode() {
return code - 1;
}
public static SearchResultCode fromCode(int code) {
if (code == 0) {
return EQUALS;
} else if (code > 0) {
return AFTER;
} else {
return BEFORE;
}
}
}
public enum LockMode {
/* see InnoDB.ib_lck_mode_t */
INTENTION_SHARED(HailDB.ib_lck_mode_t.IB_LOCK_IS), INTENTION_EXCLUSIVE(
HailDB.ib_lck_mode_t.IB_LOCK_IX), LOCK_SHARED(
HailDB.ib_lck_mode_t.IB_LOCK_S), LOCK_EXCLUSIVE(
HailDB.ib_lck_mode_t.IB_LOCK_X), NOT_USED(
HailDB.ib_lck_mode_t.IB_LOCK_NOT_USED), NONE(
HailDB.ib_lck_mode_t.IB_LOCK_NONE);
private final int code;
private LockMode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public static LockMode fromCode(int code) {
return LockMode.values()[code];
}
}
private final PointerByReference crsr;
private final TableDef table;
private final IndexDef index;
private volatile int err = HailDB.db_err.DB_SUCCESS;
public Cursor(PointerByReference crsr, TableDef table, IndexDef index) {
this.crsr = crsr;
this.table = table;
this.index = index;
}
public PointerByReference getCrsr() {
return crsr;
}
public Tuple createClusteredIndexReadTuple() {
// if (this.index != null) {
// throw new IllegalArgumentException(
// "Secondary index cursor may not create cluster read tuples");
// }
return new Tuple(HailDB.ib_clust_read_tuple_create(crsr.getValue()),
table.getColDefs());
}
public Tuple createClusteredIndexSearchTuple(Map<String, Object> val) {
if (this.index != null) {
throw new IllegalArgumentException(
"Secondary index cursor may not create cluster search tuples");
}
Tuple searchTuple = new Tuple(HailDB.ib_clust_search_tuple_create(crsr
.getValue()), table.getColDefs());
for (int i = 0; i < table.getPrimaryIndex().getColumns().size(); i++) {
ColumnDef colDef = table.getPrimaryIndex().getColumns().get(i);
Object value = val.get(colDef.getName());
setValue(searchTuple, colDef, i, value, true, true);
}
return searchTuple;
}
public Tuple createSecondaryIndexReadTuple() {
if (this.index == null) {
throw new IllegalArgumentException(
"Clustered index cursor may not create secondary index read tuples");
}
return new Tuple(HailDB.ib_sec_read_tuple_create(crsr.getValue()),
index.getColumns());
}
public Tuple createSecondaryIndexSearchTuple(Map<String, Object> val) {
if (this.index == null) {
throw new IllegalArgumentException(
"Clustered index cursor may not create secondary index search tuples");
}
Tuple searchTuple = new Tuple(HailDB.ib_sec_search_tuple_create(crsr
.getValue()), index.getColumns());
for (int i = 0; i < index.getColumns().size(); i++) {
ColumnDef colDef = index.getColumns().get(i);
Object value = val.get(colDef.getName());
setValue(searchTuple, colDef, i, value, true, true);
}
return searchTuple;
}
//open a innodb secondary index cursor and return a cursor to handle it
public Cursor openIndex(String indexName) {
if (this.index != null) {
throw new IllegalArgumentException(
"cannot open index from a secondary index cursor");
}
if (!this.table.getIndexDefs().containsKey(indexName)) {
throw new IllegalArgumentException("unknown index: " + indexName);
}
PointerByReference indexCrsr = new PointerByReference();
Util.assertSuccess(HailDB.ib_cursor_open_index_using_name(
crsr.getValue(), indexName, indexCrsr));
return new Cursor(indexCrsr, table, table.getIndexDefs().get(indexName));
}
public void setClusterAccess() {
HailDB.ib_cursor_set_cluster_access(crsr.getValue());
}
public void setMatchMode(MatchMode matchMode) {
HailDB.ib_cursor_set_match_mode(crsr.getValue(), matchMode.getCode());
}
public SearchResultCode find(Tuple tupl, SearchMode searchMode) {
IntBuffer result = ByteBuffer.allocateDirect(4).asIntBuffer();
err = HailDB.ib_cursor_moveto(crsr.getValue(), tupl.tupl,
searchMode.getCode(), result);
assertCursorState(err);
return SearchResultCode.fromCode(result.get());
}
public void readRow(Tuple tupl) {
if (!this.isPositioned()) {
throw new IllegalStateException("no row at cursor!");
}
err = HailDB.ib_cursor_read_row(crsr.getValue(), tupl.tupl);
assertCursorState(err);
}
public boolean hasNext() {
return (err == HailDB.db_err.DB_SUCCESS);
}
public boolean isPositioned() {
return (HailDB.ib_cursor_is_positioned(crsr.getValue()) == HailDB.IB_TRUE);
}
public void deleteRow() {
err = HailDB.ib_cursor_delete_row(crsr.getValue());
assertCursorState(err);
}
public void first() {
err = HailDB.ib_cursor_first(crsr.getValue());
assertCursorState(err);
}
public void last() {
err = HailDB.ib_cursor_last(crsr.getValue());
assertCursorState(err);
}
public void prev() {
err = HailDB.ib_cursor_prev(crsr.getValue());
assertCursorState(err);
}
public void next() {
err = HailDB.ib_cursor_next(crsr.getValue());
assertCursorState(err);
}
public void lock(LockMode mode) {
Util.assertSuccess(HailDB.ib_cursor_lock(crsr.getValue(),
mode.getCode()));
}
public void setLockMode(LockMode mode) {
Util.assertSuccess(HailDB.ib_cursor_set_lock_mode(crsr.getValue(),
mode.getCode()));
}
public void insertRow(Tuple tupl, Map<String, Object> data) throws InnoException {
if (data.size() != tupl.columns.size()) {
throw new IllegalArgumentException("Must specify all column values");
}
try {
for (int i = 0; i < table.getColDefs().size(); i++) {
ColumnDef colDef = table.getColDefs().get(i);
Object value = data.get(colDef.getName());
setValue(tupl, colDef, i, value, false, true);
}
Util.assertSuccess(HailDB.ib_cursor_insert_row(crsr.getValue(),
tupl.tupl));
} catch (InnoException exception) {
throw exception;
} finally {
tupl.clear();
}
}
public void updateRow(Tuple oldTuple, Map<String, Object> data) {
if (data.size() != oldTuple.columns.size()) {
throw new IllegalArgumentException("Must specify all column values");
}
Tuple newTuple = this.createClusteredIndexReadTuple();
try {
Util.assertSuccess(HailDB.ib_tuple_copy(newTuple.tupl,
oldTuple.tupl));
for (int i = 0; i < table.getColDefs().size(); i++) {
ColumnDef colDef = table.getColDefs().get(i);
Object value = data.get(colDef.getName());
setValue(newTuple, colDef, i, value, false, true);
}
Util.assertSuccess(HailDB.ib_cursor_update_row(crsr.getValue(),
oldTuple.tupl, newTuple.tupl));
} finally {
oldTuple.clear();
newTuple.delete();
}
}
//不加synchronized这里多线程会deadlock
synchronized private static void setValue(Tuple tupl, ColumnDef colDef, int i,
Object val, boolean ignoreNull, boolean coerce) {
if (val == null) {
if (!ignoreNull
&& colDef.getAttrs().contains(ColumnAttribute.NOT_NULL)) {
throw new IllegalArgumentException(
"Cannot store null in non-null column: "
+ colDef.getName());
} else {
Util.assertSuccess(HailDB.ib_col_set_value(tupl.tupl, i,
Pointer.NULL, HailDB.IB_SQL_NULL));
}
} else {
if (val instanceof String) {
val = TupleStorage.coerceType((String) val, colDef.getType());
}
switch (colDef.getType()) {
case BINARY:
case VARBINARY:
case BLOB:
TupleStorage.storeBytes(tupl, i, (byte[]) val);
break;
case CHAR:
case CHAR_ANYCHARSET:
case VARCHAR:
case VARCHAR_ANYCHARSET:
TupleStorage.storeString(tupl, i, (String) val);
break;
case INT:
Number numVal = (Number) val;
TupleStorage.storeInteger(tupl, colDef, i, numVal);
break;
case DOUBLE:
Number dubVal = (Number) val;
Util.assertSuccess(HailDB.ib_tuple_write_double(tupl.tupl, i,
dubVal.doubleValue()));
break;
case FLOAT:
Number fltVal = (Number) val;
Util.assertSuccess(HailDB.ib_tuple_write_float(tupl.tupl, i,
fltVal.floatValue()));
break;
default:
throw new IllegalArgumentException("unsupported type : "
+ colDef.getType());
}
}
}
public void reset() {
Util.assertSuccess(HailDB.ib_cursor_reset(crsr.getValue()));
}
public void close() {
Util.assertSuccess(HailDB.ib_cursor_close(crsr.getValue()));
}
private static void assertCursorState(int err) {
if (err != HailDB.db_err.DB_SUCCESS
&& err != HailDB.db_err.DB_END_OF_INDEX
&& err != HailDB.db_err.DB_RECORD_NOT_FOUND) {
throw new IllegalStateException("Cursor in invalid state (code "
+ err + ")");
}
}
}