package com.planet_ink.fakedb;
/*
Copyright 2001 Thomas Neumann
Copyright 2004-2016 Bo Zimmerman
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import java.io.*;
import java.util.*;
@SuppressWarnings({ "unchecked", "rawtypes" })
public class Backend
{
public static enum StatementType
{
SELECT, INSERT, UPDATE, DELETE
}
File basePath;
private Map<String, FakeTable> fakeTables = new HashMap<String, FakeTable>();
/**
*
*/
protected static class FakeColumn
{
String name;
int type;
boolean canNull;
int keyNumber = -1;
int indexNumber = -1;
String tableName;
public static final int TYPE_UNKNOWN = 0;
public static final int TYPE_INTEGER = 1;
public static final int TYPE_STRING = 2;
public static final int TYPE_LONG = 3;
public static final int INDEX_COUNT = Integer.MAX_VALUE;
}
/**
*
*/
protected static class RecordInfo
{
int offset;
int size;
ComparableValue[] indexedData = null;
RecordInfo(int o, int s)
{
offset = o;
size = s;
}
}
/**
*
*/
public void clearFakeTables()
{
basePath = null;
if (fakeTables != null)
for (final FakeTable R : fakeTables.values())
R.close();
fakeTables = new HashMap<String, FakeTable>();
}
/**
*
* @author Bo Zimmerman
*
*/
public static enum ConnectorType
{
AND, OR
}
/**
*
* @author Bo Zimmerman
*
*/
public class FakeCondition
{
public int conditionIndex;
public ComparableValue conditionValue;
public String lowStr = null;
public boolean like = false;
public boolean eq = false;
public boolean lt = false;
public boolean gt = false;
public boolean not = false;
public boolean unPrepared = false;
public int colType = 0;
public ConnectorType connector = ConnectorType.AND;
public List<FakeCondition> contains = null;
public boolean compareValue(ComparableValue subKey)
{
if (subKey == null)
subKey = new ComparableValue(null);
if (like && conditionValue.getValue() instanceof String)
{
if (lowStr == null)
lowStr = ((String) conditionValue.getValue()).toLowerCase();
boolean chk = false;
if (lowStr.length() == 0)
chk = conditionValue.equals(subKey);
else
if (subKey.equals(null) || (!(subKey.getValue() instanceof String)))
chk = false;
else
{
final String s = ((String) subKey.getValue()).toLowerCase();
final int x = lowStr.indexOf('%');
if ((x < 0) || (lowStr.length() == 1))
chk = lowStr.equals(s);
else
if (x == 0)
{
if (lowStr.charAt(lowStr.length() - 1) == '%')
chk = (s.indexOf(lowStr.substring(1, lowStr.length() - 1)) >= 0);
else
chk = s.startsWith(lowStr.substring(1));
}
else
if (lowStr.charAt(lowStr.length() - 1) == '%')
chk = s.endsWith(lowStr.substring(0, lowStr.length() - 1));
else
chk = s.startsWith(lowStr.substring(0, x)) && s.endsWith(lowStr.substring(x + 1));
}
return not ? !chk : chk;
}
final int sc = (lt || gt) ? subKey.compareTo(conditionValue) : 0;
if (!(((eq) && (subKey.equals(conditionValue))) || ((lt) && (sc < 0)) || ((gt) && (sc > 0))))
return not;
return !not;
}
}
/**
*
* @author Bo Zimmerman
*
*/
public interface FakeConditionResponder
{
public void callBack(ComparableValue[] values, RecordInfo info) throws Exception;
}
/**
*
* @author Bo Zimmerman
*
*/
public static class ComparableValue implements Comparable
{
private Comparable v;
public ComparableValue(Comparable v)
{
if (v instanceof ComparableValue)
this.v = ((ComparableValue) v).v;
else
this.v = v;
}
@Override
public int hashCode()
{
if (v != null)
return v.hashCode();
return 0;
}
public Comparable getValue()
{
return v;
}
@Override
public boolean equals(Object o)
{
Object t = o;
if (o instanceof ComparableValue)
t = ((ComparableValue) o).getValue();
if ((v == null) && (t == null))
return true;
if ((v == null) || (t == null))
return false;
return v.equals(t);
}
@Override
public int compareTo(Object o)
{
Object to = o;
if (o instanceof ComparableValue)
to = ((ComparableValue) o).v;
if ((v == null) && (to == null))
return 0;
if (v == null)
return -1;
if (to == null)
return 1;
return v.compareTo(to);
}
}
/**
*
* @param fakeTable
* @param columns
* @param sqlValues
* @return
* @throws java.sql.SQLException
*/
public void dupKeyCheck(final String tableName, final String[] doCols, final String[] sqlValues) throws java.sql.SQLException
{
final FakeTable fakeTable = fakeTables.get(tableName);
if (fakeTable == null)
throw new java.sql.SQLException("unknown table " + tableName);
final List<Backend.FakeCondition> conditions = new ArrayList<Backend.FakeCondition>(2);
for (int i = 0; i < doCols.length; i++)
{
final int id = fakeTable.findColumn(doCols[i]);
if (id < 0)
continue;
final FakeColumn col = fakeTable.columns[id];
if (col.keyNumber >= 0)
{
final Backend.FakeCondition condition = buildFakeCondition(fakeTable.name, col.name, "=", sqlValues[i], false);
condition.connector = Backend.ConnectorType.AND;
conditions.add(condition);
}
}
if (conditions.size() == 0)
return;
final FakeConditionResponder responder = new FakeConditionResponder()
{
@Override
public void callBack(ComparableValue[] values, RecordInfo info) throws Exception
{
throw new java.sql.SQLException("duplicate key error");
}
};
try
{
fakeTable.recordIterator(conditions, responder);
}
catch (final Exception e)
{
throw new java.sql.SQLException(e.getMessage());
}
}
protected static class IndexedRowMapComparator implements Comparator
{
private final int index;
private final boolean descending;
public IndexedRowMapComparator(int index, boolean descending)
{
this.index = index;
this.descending = descending;
}
@Override
public int compare(Object arg0, Object arg1)
{
final RecordInfo inf0 = (RecordInfo) arg0;
final RecordInfo inf1 = (RecordInfo) arg1;
if (descending)
return inf1.indexedData[index].compareTo(inf0.indexedData[index]);
else
return inf0.indexedData[index].compareTo(inf1.indexedData[index]);
}
}
/**
*
* @author Bo Zimmerman
*
*/
protected static class IndexedRowMap
{
private final Vector<RecordInfo> unsortedRecords = new Vector<RecordInfo>();
private List<RecordInfo>[] forwardSorted = null;
private List<RecordInfo>[] reverseSorted = null;
private IndexedRowMapComparator[] forwardComparators = null;
private IndexedRowMapComparator[] reverseComparators = null;
private static final List<RecordInfo> empty = new ArrayList<RecordInfo>(1);
public synchronized void add(RecordInfo record)
{
unsortedRecords.add(record);
clearSortCaches(record.indexedData.length);
}
public synchronized void remove(RecordInfo record)
{
unsortedRecords.remove(record);
clearSortCaches(record.indexedData.length);
}
private void clearSortCaches(int size)
{
forwardSorted = new List[size];
reverseSorted = new List[size];
if (forwardComparators == null)
{
forwardComparators = new IndexedRowMapComparator[size];
for (int i = 0; i < size; i++)
forwardComparators[i] = new IndexedRowMapComparator(i, false);
}
if (reverseComparators == null)
{
reverseComparators = new IndexedRowMapComparator[size];
for (int i = 0; i < size; i++)
reverseComparators[i] = new IndexedRowMapComparator(i, true);
}
}
public synchronized Iterator<RecordInfo> iterator(int sortIndex, boolean descending)
{
Iterator iter = null;
if (sortIndex < 0)
iter = Arrays.asList(unsortedRecords.toArray()).iterator();
else
{
final List<RecordInfo>[] whichList = descending ? reverseSorted : forwardSorted;
if ((whichList == null) || (sortIndex < 0) || (sortIndex >= whichList.length))
iter = empty.iterator();
else
{
synchronized (whichList)
{
if (whichList[sortIndex] != null)
iter = whichList[sortIndex].iterator();
else
{
final IndexedRowMapComparator comparator = descending ? reverseComparators[sortIndex] : forwardComparators[sortIndex];
final List<RecordInfo> newList = (List<RecordInfo>) unsortedRecords.clone();
Collections.sort(newList, comparator);
whichList[sortIndex] = newList;
iter = newList.iterator();
}
}
}
}
return iter;
}
}
/**
*
*/
protected static class FakeTable
{
private File fileName;
private final String name;
private RandomAccessFile file;
private int fileSize;
private byte[] fileBuffer;
private FakeColumn[] columns;
private Map<String, Integer> columnHash = new Hashtable<String, Integer>();
private int[] columnIndexesOfIndexed;
private IndexedRowMap rowRecords = new IndexedRowMap();
FakeTable(String tableName, File name)
{
this.name = tableName;
fileName = name;
}
protected int numColumns()
{
return columns.length;
}
/**
*
* @param name
* @return
*/
protected int findColumn(String name)
{
if ((name != null) && (columnHash.containsKey(name)))
return columnHash.get(name).intValue();
return -1;
}
/**
*
* @param orderByIndexDex
* @param orderByConditions
* @return
*/
public Iterator<RecordInfo> indexIterator(int[] orderByIndexDex, String[] orderByConditions)
{
if ((orderByIndexDex == null) || (orderByIndexDex.length == 0))
return rowRecords.iterator(-1, false);
final boolean descending = (orderByConditions != null) && "DESC".equals(orderByConditions[0]);
final FakeColumn col = columns[orderByIndexDex[0]];
return rowRecords.iterator(col.indexNumber, descending);
}
/**
*
* @param index
* @return
*/
protected String getColumnName(int index)
{
if ((index < 0) || (index > columns.length))
return null;
return columns[index].name;
}
/**
*
* @param index
* @return
*/
public FakeColumn getColumnInfo(int index)
{
if ((index < 0) || (index > columns.length))
return null;
return columns[index];
}
protected void close()
{
fileName = null;
if (file != null)
{
try
{
file.close();
}
catch (final Exception e)
{
}
file = null;
}
columns = null;
columnHash = null;
columnIndexesOfIndexed = null;
rowRecords = new IndexedRowMap();
}
/**
*
* @throws IOException
*/
protected void open() throws IOException
{
file = new RandomAccessFile(fileName, "rw");
fileSize = 0;
fileBuffer = new byte[4096];
int remaining = 0;
int ofs = 0;
int found = 0, skipped = 0;
while (true)
{
if (remaining == 0)
{
ofs = 0;
remaining = file.read(fileBuffer);
if (remaining < 0)
break;
}
boolean skip;
if (fileBuffer[ofs] == '-') // deleted
{
skip = false;
}
else
if (fileBuffer[ofs] == '*') // active
{
skip = true;
}
else
break;
// check if valid...
boolean valid = true;
int size = 0;
while (true)
{
int toCheck = columns.length + 1;
for (int index = ofs, left = remaining; left > 0; left--, index++)
{
if (fileBuffer[index] == 0x0A)
{
if (--toCheck == 0)
{
size = index - ofs + 1;
break;
}
}
}
if (toCheck == 0)
break;
if (ofs > 0)
{
System.arraycopy(fileBuffer, ofs, fileBuffer, 0, remaining);
ofs = 0;
}
if (ofs + remaining == fileBuffer.length)
{
final byte[] newFileBuffer = new byte[fileBuffer.length * 2];
System.arraycopy(fileBuffer, 0, newFileBuffer, 0, remaining);
fileBuffer = newFileBuffer;
}
final int additional = file.read(fileBuffer, remaining, fileBuffer.length - remaining);
if (additional < 0)
{
valid = false;
break;
}
remaining += additional;
}
if (!valid)
break;
// Build index string
if (!skip)
{
int current = -1;
FakeColumn col = null;
final int[] sub = new int[] { ofs };
final ComparableValue[] indexData = new ComparableValue[columnIndexesOfIndexed.length];
for (int index = 0; index < columnIndexesOfIndexed.length; index++)
{
while (current < columnIndexesOfIndexed[index])
{
while (fileBuffer[sub[0]] != 0x0A)
sub[0]++;
sub[0]++;
current++;
}
col = columns[columnIndexesOfIndexed[index]];
indexData[index] = getNextLine(col.type, fileBuffer, sub);
}
final RecordInfo info = new RecordInfo(fileSize, size);
info.indexedData = indexData;
rowRecords.add(info);
}
else
skipped += size;
found += size;
// Fix pointers
ofs += size;
remaining -= size;
fileSize += size;
}
// Too much space wasted?
if (skipped > (found / 10))
vacuum();
}
/**
*
* @throws IOException
*/
private void vacuum() throws IOException
{
final File tempFileName = new File(fileName.getName() + ".tmp");
final File tempFileName2 = new File(fileName.getName() + ".cpy");
final RandomAccessFile tempOut = new RandomAccessFile(tempFileName, "rw");
int newFileSize = 0;
for (final Iterator<RecordInfo> iter = rowRecords.iterator(-1, false); iter.hasNext();)
{
final RecordInfo info = iter.next();
file.seek(info.offset);
file.readFully(fileBuffer, 0, info.size);
tempOut.write(fileBuffer, 0, info.size);
info.offset = newFileSize;
newFileSize += info.size;
}
tempOut.getFD().sync();
tempOut.close();
file.close();
tempFileName2.delete();
fileName.renameTo(tempFileName2);
tempFileName.renameTo(fileName);
tempFileName2.delete();
file = new RandomAccessFile(fileName, "rw");
fileSize = newFileSize;
}
/**
*
* @param values
* @param info
* @return
*/
protected synchronized boolean getRecord(ComparableValue[] values, RecordInfo info)
{
try
{
file.seek(info.offset);
file.readFully(fileBuffer, 0, info.size);
final int[] ofs = new int[] { 0 };
FakeColumn col = null;
for (int index = 0; index < columns.length; index++)
{
col = columns[index];
while (fileBuffer[ofs[0]] != 0x0A)
ofs[0]++;
ofs[0]++;
values[index] = getNextLine(col.type, fileBuffer, ofs);
}
return true;
}
catch (final IOException e)
{
return false;
}
}
/**
*
* @param colType
* @param fileBuffer
* @param dex
* @return
*/
public ComparableValue getNextLine(int colType, byte[] fileBuffer, int[] dex)
{
if ((fileBuffer[dex[0]] == '\\') && (fileBuffer[dex[0] + 1] == '?'))
return new ComparableValue(null);
else
{
final StringBuilder buffer = new StringBuilder("");
for (;; dex[0]++)
{
char c = (char) (fileBuffer[dex[0]] & 0xFF);
if (c == 0x0A)
break;
if (c == '\\')
{
if (fileBuffer[dex[0] + 1] == '\\')
{
buffer.append('\\');
dex[0]++;
}
else
if (fileBuffer[dex[0] + 1] == 'n')
{
buffer.append((char) 0x0A);
dex[0]++;
}
else
{
int val = 0;
for (int i = 0; i < 4; i++)
{
c = (char) (fileBuffer[++dex[0]] & 0xFF);
if (c >= 'A')
val = (16 * val) + (c - 'A');
else
val = (16 * val) + (c - '0');
}
}
}
else
buffer.append(c);
}
if (buffer.toString().equals("null"))
return new ComparableValue(null);
else
{
switch (colType)
{
case FakeColumn.TYPE_INTEGER:
return new ComparableValue(Integer.valueOf(buffer.toString()));
case FakeColumn.TYPE_LONG:
return new ComparableValue(Long.valueOf(buffer.toString()));
default:
return new ComparableValue(buffer.toString());
}
}
}
}
/**
*
* @param required
*/
private void increaseBuffer(int required)
{
final int newSize = ((required + 4095) >>> 12) << 12;
final byte[] newBuffer = new byte[newSize];
System.arraycopy(fileBuffer, 0, newBuffer, 0, fileBuffer.length);
fileBuffer = newBuffer;
}
/**
*
* @param prevRecord
* @param indexData
* @param values
* @return
*/
protected synchronized boolean insertRecord(RecordInfo prevRecord, ComparableValue[] indexData, ComparableValue[] values)
{
try
{
int ofs = 2;
fileBuffer[0] = (byte) '-';
fileBuffer[1] = (byte) 0x0A;
for (final ComparableValue value : values)
{
if ((value == null) || (value.getValue() == null))
{
if (ofs + 3 > fileBuffer.length)
increaseBuffer(ofs + 3);
fileBuffer[ofs + 0] = (byte) '\\';
fileBuffer[ofs + 1] = (byte) '?';
fileBuffer[ofs + 2] = (byte) 0x0A;
ofs += 3;
}
else
{
int size = 0;
final String s = value.getValue().toString();
for (int sub = 0; sub < s.length(); sub++)
{
final char c = s.charAt(sub);
if (c == '\\')
size += 2;
else
if (c == '\n')
size += 2;
else
if (c > 255)
size += 5;
else
size++;
}
if (ofs + size + 1 > fileBuffer.length)
increaseBuffer(ofs + size + 1);
for (int sub = 0; sub < s.length(); sub++)
{
char c = s.charAt(sub);
if (c == '\\')
{
fileBuffer[ofs] = (byte) '\\';
fileBuffer[ofs + 1] = (byte) '\\';
ofs += 2;
}
else
if (c == '\n')
{
fileBuffer[ofs] = (byte) '\\';
fileBuffer[ofs + 1] = (byte) 'n';
ofs += 2;
}
else
if (c > 255)
{
fileBuffer[ofs++] = (byte) '\\';
for (int i = 0; i < 4; i++)
{
fileBuffer[ofs++] = (byte) ("0123456789ABCDEF".charAt(c >>> 12));
c <<= 4;
}
}
else
fileBuffer[ofs++] = (byte) c;
}
fileBuffer[ofs++] = (byte) 0x0A;
}
}
int recordPos = fileSize;
if ((prevRecord != null) && (prevRecord.size == ofs))
recordPos = prevRecord.offset;
else
fileSize += ofs;
file.seek(recordPos);
file.write(fileBuffer, 0, ofs);
file.getFD().sync();
final RecordInfo info = new RecordInfo(recordPos, ofs);
info.indexedData = indexData;
rowRecords.add(info);
return true;
}
catch (final IOException e)
{
return false;
}
}
/**
*
* @param conditions
* @return
*/
protected synchronized int deleteRecord(List<FakeCondition> conditions)
{
final int[] count = { 0 };
try
{
final FakeConditionResponder responder = new FakeConditionResponder()
{
public int[] count;
public FakeConditionResponder init(int[] c)
{
count = c;
return this;
}
@Override
public void callBack(ComparableValue[] values, RecordInfo info) throws Exception
{
file.seek(info.offset);
file.write(new byte[] { (byte) '*' });
rowRecords.remove(info);
count[0]++;
}
}.init(count);
recordIterator(conditions, responder);
}
catch (final Exception e)
{
e.printStackTrace();
return -1;
}
return count[0];
}
/**
*
* @param info
* @param conditions
* @param dataLoaded
* @param values
* @return
*/
public boolean recordCompare(RecordInfo info, List<FakeCondition> conditions, boolean[] dataLoaded, ComparableValue[] values)
{
boolean lastOne = true;
ConnectorType connector = ConnectorType.AND;
final ComparableValue[] rowIndexesData = info.indexedData;
for (final FakeCondition cond : conditions)
{
boolean thisOne = false;
if (cond.contains != null)
thisOne = recordCompare(info, cond.contains, dataLoaded, values);
else
{
final FakeColumn column = columns[cond.conditionIndex];
if (column.indexNumber >= 0)
thisOne = cond.compareValue(rowIndexesData[column.indexNumber]);
else
{
if (!dataLoaded[0])
dataLoaded[0] = getRecord(values, info);
if (dataLoaded[0])
{
if (values[cond.conditionIndex] == null)
thisOne = false;
else
if (cond.not)
thisOne = !cond.compareValue(values[cond.conditionIndex]);
else
thisOne = cond.compareValue(values[cond.conditionIndex]);
}
}
}
if (connector == ConnectorType.OR)
lastOne = lastOne || thisOne;
else
lastOne = lastOne && thisOne;
connector = cond.connector;
}
return lastOne;
}
/**
*
* @param conditions
* @param callBack
*/
public void recordIterator(List<FakeCondition> conditions, FakeConditionResponder callBack) throws Exception
{
final boolean[] dataLoaded = new boolean[1];
final ComparableValue[] values = new ComparableValue[columns.length];
for (final Iterator<RecordInfo> iter = rowRecords.iterator(-1, false); iter.hasNext();)
{
final RecordInfo info = iter.next();
dataLoaded[0] = false;
if (recordCompare(info, conditions, dataLoaded, values))
{
if (!dataLoaded[0])
dataLoaded[0] = getRecord(values, info);
if (dataLoaded[0])
callBack.callBack(values, info);
}
}
}
/**
*
* @param conditions
* @param columns
* @param values
* @return
*/
protected synchronized int updateRecord(List<FakeCondition> conditions, int[] columns, ComparableValue[] values)
{
final int[] count = { 0 };
try
{
final FakeConditionResponder responder = new FakeConditionResponder() {
public int[] count;
public int[] newCols;
public ComparableValue[] updatedValues = null;
public FakeConditionResponder init(int[] c, int[] a, ComparableValue[] n)
{
count = c;
newCols = a;
updatedValues = n;
return this;
}
@Override
public void callBack(ComparableValue[] values, RecordInfo info) throws Exception
{
final ComparableValue[] rowIndexData = info.indexedData;
boolean somethingChanged = false;
for (int sub = 0; sub < newCols.length; sub++)
{
if (!values[newCols[sub]].equals(updatedValues[sub]))
{
values[newCols[sub]] = updatedValues[sub];
somethingChanged = true;
}
for (int k = 0; k < rowIndexData.length; k++)
if (columnIndexesOfIndexed[k] == newCols[sub])
rowIndexData[k] = updatedValues[sub];
}
if (somethingChanged)
{
file.seek(info.offset);
file.write(new byte[] { (byte) '*' });
rowRecords.remove(info);
insertRecord(info, rowIndexData, values);
}
count[0]++;
}
}.init(count, columns, values);
recordIterator(conditions, responder);
}
catch (final Exception e)
{
e.printStackTrace();
return -1;
}
return count[0];
}
}
/**
*
* @param basePath
* @param schema
* @throws IOException
*/
private void readSchema(File basePath, File schema) throws IOException
{
final BufferedReader in = new BufferedReader(new FileReader(schema));
try
{
while (true)
{
final String fakeTableName = in.readLine();
if (fakeTableName == null)
break;
if (fakeTableName.length() == 0)
throw new IOException("Can not read schema: tableName is null");
if (fakeTableName.startsWith("#")) // comment
continue;
if (fakeTables.get(fakeTableName) != null)
throw new IOException("Can not read schema: tableName is missing: " + fakeTableName);
final List<FakeColumn> columns = new Vector<FakeColumn>();
final List<String> keys = new Vector<String>();
final List<String> indexes = new Vector<String>();
while (true)
{
String line = in.readLine();
if (line == null)
break;
if (line.length() == 0)
break;
int split = line.indexOf(' ');
if (split < 0)
throw new IOException("Can not read schema: expected space in line '" + line + "'");
final String columnName = line.substring(0, split);
line = line.substring(split + 1);
split = line.indexOf(' ');
String columnType;
String[] columnModifiers = null;
if (split < 0)
{
columnType = line;
columnModifiers = new String[0];
}
else
{
columnType = line.substring(0, split);
final String lineRes = line.substring(split + 1).trim();
final int split2 = lineRes.indexOf(' ');
if (split2 > 0)
columnModifiers = new String[] { lineRes.substring(0, split2).trim(), lineRes.substring(split2 + 1).trim() };
else
columnModifiers = new String[] { lineRes };
}
final FakeColumn info = new FakeColumn();
info.tableName = fakeTableName;
info.name = columnName;
if (columnType.equals("string"))
info.type = FakeColumn.TYPE_STRING;
else
if (columnType.equals("integer"))
info.type = FakeColumn.TYPE_INTEGER;
else
if (columnType.equals("long"))
info.type = FakeColumn.TYPE_LONG;
else
if (columnType.equals("datetime"))
info.type = FakeColumn.TYPE_LONG;
else
throw new IOException("Can not read schema: attributeType '" + columnType + "' is unknown");
for (final String modifier : columnModifiers)
{
if (modifier.equals(""))
continue;
else
if (modifier.equals("NULL"))
info.canNull = true;
else
if (modifier.equals("KEY"))
{
info.keyNumber = keys.size();
keys.add(columnName);
info.indexNumber = indexes.size();
indexes.add(columnName);
}
else
if (modifier.equals("INDEX"))
{
info.indexNumber = indexes.size();
indexes.add(columnName);
}
else
throw new IOException("Can not read schema: attributeSpecial '" + modifier + "' is unknown");
}
columns.add(info);
}
final FakeTable fakeTable = new FakeTable(fakeTableName, new File(basePath, "fakedb.data." + fakeTableName));
fakeTable.columns = new FakeColumn[columns.size()];
fakeTable.columnHash = new Hashtable<String, Integer>();
int index = 0;
for (final Iterator iter = columns.iterator(); iter.hasNext(); ++index)
{
final FakeColumn current = (FakeColumn) iter.next();
fakeTable.columns[index] = current;
fakeTable.columnHash.put(current.name, Integer.valueOf(index));
}
index = 0;
fakeTable.columnIndexesOfIndexed = new int[indexes.size()];
for (final Iterator iter = indexes.iterator(); iter.hasNext(); ++index)
fakeTable.columnIndexesOfIndexed[index] = fakeTable.findColumn((String) iter.next());
fakeTable.open();
fakeTables.put(fakeTableName, fakeTable);
}
}
finally
{
in.close();
}
}
/**
*
* @param basePath
* @return
*/
protected boolean open(File basePath)
{
try
{
readSchema(basePath, new File(basePath, "fakedb.schema"));
return true;
}
catch (final IOException e)
{
e.printStackTrace();
return false;
}
}
/**
*
* @param s
* @param tableName
* @param cols
* @param conditions
* @param orderVars
* @param orderModifiers
* @return
* @throws java.sql.SQLException
*/
protected java.sql.ResultSet constructScan(final ImplSelectStatement stmt) throws java.sql.SQLException
{
final Statement s = stmt.s;
final String tableName = stmt.tableName;
final List<String> cols = stmt.cols;
final List<Backend.FakeCondition> conditions = stmt.conditions;
final String[] orderVars = stmt.orderVars;
final String[] orderModifiers = stmt.orderModifiers;
final FakeTable table = fakeTables.get(tableName);
if (table == null)
throw new java.sql.SQLException("unknown table " + tableName);
int[] showCols;
if ((cols.size() == 0) || (cols.contains("*")))
{
showCols = new int[table.numColumns()];
for (int i = 0; i < showCols.length; i++)
showCols[i] = i;
}
else
{
int index = 0;
showCols = new int[cols.size()];
for (final String col : cols)
{
if (col.toLowerCase().startsWith("count("))
showCols[index] = FakeColumn.INDEX_COUNT;
else
{
showCols[index] = table.findColumn(col);
if (showCols[index] < 0)
{
try
{
Integer.parseInt(col);
showCols[index] = FakeColumn.INDEX_COUNT;
}
catch (final Exception e)
{
throw new java.sql.SQLException("unknown column " + tableName + "." + col);
}
}
}
index++;
}
}
int[] orderDexIndexes = null;
if (orderVars != null)
{
orderDexIndexes = new int[orderVars.length];
int d = 0;
for (final String var : orderVars)
{
final int index = table.findColumn(var);
int indexDex = -1;
if (index < 0)
throw new java.sql.SQLException("unknown column " + var);
for (final int i : table.columnIndexesOfIndexed)
if (i == index)
indexDex = i;
if (indexDex < 0)
throw new java.sql.SQLException("unable to order by non-indexed " + var);
orderDexIndexes[d] = indexDex;
d++;
}
}
return new ResultSet(s, table, showCols, conditions, orderDexIndexes, orderModifiers);
}
/**
* For prepared statements, an abstract way into things
*
* @author Bo Zimmermanimmerman
*/
public static abstract class ImplAbstractStatement
{
public abstract String[] values();
public abstract Boolean[] unPreparedValuesFlags();
public abstract List<FakeCondition> conditions();
public abstract StatementType getStatementType();
}
/**
* Parameters to execute an insert statement
*
* @author Bo Zimmermanimmerman
*/
public static class ImplInsertStatement extends ImplAbstractStatement
{
public ImplInsertStatement(final String tableName, final String[] columns, final String[] sqlValues, final Boolean[] unPreparedValues)
{
this.tableName = tableName;
this.columns = columns;
this.sqlValues = sqlValues;
this.unPreparedValues = unPreparedValues;
}
public final String tableName;
public final String[] columns;
public final String[] sqlValues;
public final Boolean[] unPreparedValues;
@Override
public final String[] values()
{
return sqlValues;
}
@Override
public final List<FakeCondition> conditions()
{
return null;
}
@Override
public final Boolean[] unPreparedValuesFlags()
{
return unPreparedValues;
}
@Override
public final StatementType getStatementType()
{
return StatementType.INSERT;
}
}
/**
* Parameters to execute an update statement
*
* @author Bo Zimmermanimmerman
*/
public static class ImplUpdateStatement extends ImplAbstractStatement
{
public ImplUpdateStatement(final String tableName, final List<FakeCondition> conditions, final String[] columns, final String[] sqlValues, final Boolean[] unPreparedValues)
{
this.tableName = tableName;
this.columns = columns;
this.sqlValues = sqlValues;
this.conditions = conditions;
this.unPreparedValues = unPreparedValues;
}
public final String tableName;
public final String[] columns;
public final String[] sqlValues;
public final Boolean[] unPreparedValues;
public final List<FakeCondition> conditions;
@Override
public final String[] values()
{
return sqlValues;
}
@Override
public final List<FakeCondition> conditions()
{
return conditions;
}
@Override
public final Boolean[] unPreparedValuesFlags()
{
return unPreparedValues;
}
@Override
public final StatementType getStatementType()
{
return StatementType.UPDATE;
}
}
/**
* Parameters to execute an select statement
*
* @author Bo Zimmermanimmerman
*/
public static class ImplSelectStatement extends ImplAbstractStatement
{
public ImplSelectStatement(final Statement s, final String tableName, final List<String> cols, final List<Backend.FakeCondition> conditions, final String[] orderVars, final String[] orderModifiers)
{
this.s = s;
this.tableName = tableName;
this.cols = cols;
this.conditions = conditions;
this.orderVars = orderVars;
this.orderModifiers = orderModifiers;
}
final Statement s;
final String tableName;
final List<String> cols;
final List<Backend.FakeCondition> conditions;
final String[] orderVars;
final String[] orderModifiers;
private final Boolean[] unPreparedValues = new Boolean[0];
@Override
public final Boolean[] unPreparedValuesFlags()
{
return unPreparedValues;
}
@Override
public final String[] values()
{
return null;
}
@Override
public final List<FakeCondition> conditions()
{
return conditions;
}
@Override
public final StatementType getStatementType()
{
return StatementType.SELECT;
}
}
/**
* Parameters to execute an delete statement
*
* @author Bo Zimmermanimmerman
*/
public static class ImplDeleteStatement extends ImplAbstractStatement
{
public ImplDeleteStatement(final String tableName, final List<FakeCondition> conditions)
{
this.tableName = tableName;
this.conditions = conditions;
}
public final String tableName;
public final List<FakeCondition> conditions;
private final Boolean[] unPreparedValues = new Boolean[0];
@Override
public final Boolean[] unPreparedValuesFlags()
{
return unPreparedValues;
}
@Override
public final String[] values()
{
return null;
}
@Override
public final List<FakeCondition> conditions()
{
return conditions;
}
@Override
public final StatementType getStatementType()
{
return StatementType.DELETE;
}
}
/**
*
* @param tableName
* @param columns
* @param dataValues
* @throws java.sql.SQLException
*/
protected void insertValues(final ImplInsertStatement stmt) throws java.sql.SQLException
{
final String tableName = stmt.tableName;
final String[] columns = stmt.columns;
final String[] sqlValues = stmt.sqlValues;
final FakeTable fakeTable = fakeTables.get(tableName);
if (fakeTable == null)
throw new java.sql.SQLException("unknown table " + tableName);
final ComparableValue[] values = new ComparableValue[fakeTable.columns.length];
for (int index = 0; index < columns.length; index++)
{
final int id = fakeTable.findColumn(columns[index]);
if (id < 0)
throw new java.sql.SQLException("unknown column " + columns[index]);
final FakeColumn col = fakeTable.columns[id];
try
{
if ((sqlValues[index] == null) || (sqlValues[index].equals("null")))
values[id] = new ComparableValue(null);
else
{
switch (col.type)
{
case FakeColumn.TYPE_INTEGER:
values[id] = new ComparableValue(Integer.valueOf(sqlValues[index]));
break;
case FakeColumn.TYPE_LONG:
values[id] = new ComparableValue(Long.valueOf(sqlValues[index]));
break;
default:
values[id] = new ComparableValue(sqlValues[index]);
break;
}
}
}
catch (final Exception e)
{
throw new java.sql.SQLException("illegal value '" + sqlValues[index] + "' for column " + col.name);
}
}
final ComparableValue[] keys = new ComparableValue[fakeTable.columnIndexesOfIndexed.length];
for (int index = 0; index < fakeTable.columnIndexesOfIndexed.length; index++)
{
final int id = fakeTable.columnIndexesOfIndexed[index];
if (values[id] == null)
keys[index] = new ComparableValue(null);
else
keys[index] = new ComparableValue(values[id]);
}
if (!fakeTable.insertRecord(null, keys, values))
throw new java.sql.SQLException("unable to insert record");
}
/**
*
* @param tableName
* @param conditionVar
* @param conditionValue
* @throws java.sql.SQLException
*/
protected void deleteRecord(final ImplDeleteStatement stmt) throws java.sql.SQLException
{
final FakeTable fakeTable = fakeTables.get(stmt.tableName);
if (fakeTable == null)
throw new java.sql.SQLException("unknown table " + stmt.tableName);
fakeTable.deleteRecord(stmt.conditions);
}
/**
*
* @param tableName
* @param conditionVar
* @param conditionValue
* @param varNames
* @param values
* @throws java.sql.SQLException
*/
protected void updateRecord(final ImplUpdateStatement stmt) throws java.sql.SQLException
{
final String tableName = stmt.tableName;
final List<FakeCondition> conditions = stmt.conditions;
final String[] varNames = stmt.columns;
final String[] sqlValues = stmt.sqlValues;
final FakeTable fakeTable = fakeTables.get(tableName);
if (fakeTable == null)
throw new java.sql.SQLException("unknown table " + tableName);
final int[] vars = new int[varNames.length];
for (int index = 0; index < vars.length; index++)
if ((vars[index] = fakeTable.findColumn(varNames[index])) < 0)
throw new java.sql.SQLException("unknown column " + varNames[index]);
final ComparableValue[] values = new ComparableValue[fakeTable.columns.length];
for (int index = 0; index < sqlValues.length; index++)
{
final FakeColumn col = fakeTable.columns[vars[index]];
try
{
if ((sqlValues[index] == null) || (sqlValues[index].equals("null")))
values[index] = new ComparableValue(null);
else
{
switch (col.type)
{
case FakeColumn.TYPE_INTEGER:
values[index] = new ComparableValue(Integer.valueOf(sqlValues[index]));
break;
case FakeColumn.TYPE_LONG:
values[index] = new ComparableValue(Long.valueOf(sqlValues[index]));
break;
default:
values[index] = new ComparableValue(sqlValues[index]);
break;
}
}
}
catch (final Exception e)
{
throw new java.sql.SQLException("illegal value '" + sqlValues[index] + "' for column " + col.name);
}
}
fakeTable.updateRecord(conditions, vars, values);
}
/**
*
* @param tableName
* @param columnName
* @param comparitor
* @param value
* @return
* @throws java.sql.SQLException
*/
public FakeCondition buildFakeCondition(String tableName, String columnName, String comparitor, String value, boolean unPrepared) throws java.sql.SQLException
{
final FakeTable fakeTable = fakeTables.get(tableName);
if (fakeTable == null)
throw new java.sql.SQLException("unknown table " + tableName);
final FakeCondition fake = new FakeCondition();
fake.unPrepared = unPrepared;
if (columnName == null)
{
fake.conditionIndex = 0;
fake.conditionValue = new ComparableValue(null);
return fake;
}
if ((fake.conditionIndex = fakeTable.findColumn(columnName)) < 0)
throw new java.sql.SQLException("unknown column " + tableName + "." + columnName);
final FakeColumn col = fakeTable.columns[fake.conditionIndex];
if (col == null)
throw new java.sql.SQLException("bad column " + tableName + "." + columnName);
fake.colType = col.type;
if ((value == null) || value.equals("null") || unPrepared)
fake.conditionValue = new ComparableValue(null);
else
{
switch (col.type)
{
case FakeColumn.TYPE_INTEGER:
{
try
{
fake.conditionValue = new ComparableValue(Integer.valueOf(value));
}
catch (final Exception e)
{
throw new java.sql.SQLException("can't compare " + value + " to " + tableName + "." + columnName);
}
break;
}
case FakeColumn.TYPE_LONG:
{
try
{
fake.conditionValue = new ComparableValue(Long.valueOf(value));
}
catch (final Exception e)
{
throw new java.sql.SQLException("can't compare " + value + " to " + tableName + "." + columnName);
}
break;
}
default:
fake.conditionValue = new ComparableValue(value);
break;
}
}
if (comparitor.equalsIgnoreCase("like"))
{
if ((col.type != FakeColumn.TYPE_STRING) && (col.type != FakeColumn.TYPE_UNKNOWN))
throw new java.sql.SQLException("can't do like comparison on " + tableName + "." + columnName);
fake.like = true;
}
else
{
for (final char c : comparitor.toCharArray())
{
switch (c)
{
case '!':
fake.not = true;
break;
case '=':
fake.eq = true;
break;
case '<':
fake.lt = true;
break;
case '>':
fake.gt = true;
break;
}
}
}
if (fake.lt && fake.gt && (!fake.eq))
{
fake.lt = false;
fake.gt = false;
fake.not = !fake.not;
fake.eq = true;
}
return fake;
}
}