fqlite.descriptor.TableDescriptor Maven / Gradle / Ivy
package fqlite.descriptor;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import fqlite.pattern.HeaderPattern;
import fqlite.util.Auxiliary;
/**
* Objects of this class are used to represent a component.
* Besides component names and column names, regular expressions
* are also managed by this class.
*
* The latter are used to assign a record to a component.
*
* @author pawlaszc
*
*/
public class TableDescriptor extends AbstractDescriptor implements Comparable {
String regex = "";
String delregex = "";
public List serialtypes;
public List columnnames;
public List sqltypes;
public List constraints; // constraint for each column
public List tableconstraints; // constraints on table level
public List primarykeycolumns;
int size = 0;
int numberofmultibytecolumns = 0;
String signature = ""; // the type signature, i.e., INTSTRINGSTINGINT
public String tblname = "";
public int root = -1;
public boolean ROWID = true;
private HeaderPattern hpattern = null;
public boolean virtual = false;
public String modulname = null;
public String sql = "";
public String rowidcolumn = null;
public boolean checkMatch(String match) {
try
{
/* hex-String representation to byte array */
byte[] bcol = Auxiliary.decode(match);
/* interpret all byte values as a list of varints */
/* each varint represents a columntype */
long[] values = Auxiliary.readVarInt(bcol);
/*
* normally, the first byte of the match holds total length of header bytes
* including this byte
*/
int headerlength = (int) values[0];
info(Integer.toString(headerlength));
boolean valid = true;
// check serialtypes, only if all serialtypes are valid the match is also valid
for (int i = 1; i < values.length; i++) {
/* get next column */
String type = getColumntypes().get(i - 1);
switch (type) {
case "INT":
/* an INT column has always a value between 0..6 */
valid = values[i] >= 0 && values[i] <= 6;
break;
case "REAL":
/* a FLOATING POINT COLUMN is always mapped with value 7 */
valid = values[i] == 7;
break;
case "TEXT":
/* a TEXT COLUMN has always an odd value bigger 13 or is zero */
if (values[i] == 0)
valid = true;
else if (values[i] % 2 != 0)
valid = values[i] > 13;
else
valid = false;
break;
case "BLOB":
/* a BLOB COLUMN has always an even value bigger 13 or is zero */
if (values[i] == 0)
valid = true;
else if (values[i] % 2 == 0)
valid = values[i] > 12;
else
valid = false;
break;
case "NUMERIC":
valid = values[i] >= 0 && values[i] <= 9;
break;
}
/* as soon as one of the serialtypes is not valid -> discard the match */
if (!valid)
return false;
}
}
catch(Exception err)
{
/* Note: if something went wrong during validation - the match was not valid */
return false;
}
return true;
}
@Override
public String getName()
{
return this.tblname;
}
public void setModulname(String name)
{
modulname = name;
}
public String getModulename()
{
return modulname;
}
public void signature(List col) {
signature = "";
Iterator iter = getColumntypes().iterator();
while (iter.hasNext()) {
signature += iter.next();
}
// System.out.println("Signature::" + signature + " Table:: " + tblname );
}
public String getSignature() {
return signature;
}
/**
* Constructor.
*
* @param tblname name of the table
* @param stmt sql-statement
* @param sqltypes List with all SQL types from the statement
* @param col List with serial types matching the SQL types
* @param names List with column names
* @param constraints List with column constraints
* @param tableconstraints List with table constraints
* @param pattern pattern for matching the table header
* @param withoutROWID true, if the table has no ROWID
*/
public TableDescriptor(String tblname, String stmt, List sqltypes, List col, List names, List constraints, List tableconstraints, HeaderPattern pattern, boolean withoutROWID) {
setHpattern(pattern);
setColumntypes(col);
signature(col);
this.sqltypes = sqltypes;
this.constraints = constraints;
this.tableconstraints = tableconstraints;
columnnames = names;
ROWID = !withoutROWID;
sql = stmt;
this.tblname = tblname;
primarykeycolumns = new LinkedList();
/* find the primary key by checking the column constraint */
for(int i=0; i < names.size(); i++)
{
if (null != constraints)
info("tblname: " + tblname);
if (tblname.equals("__UNASSIGNED"))
break;
/* we look for the keyword PRIMARYKEY */
if (constraints.get(i).contains("PRIMARYKEY"))
{
this.primarykeycolumns.add(names.get(i));
}
}
/* check, if there is a PRIMARYKEY definition in the table constraints */
if (null != tableconstraints) {
for (int i=0; i and the
* declared type of that column is "INTEGER" in any mixture
* of upper and lower case, then the column becomes an alias
* for the rowid.
* Such a column is usually referred to as an "integer primary key".
* A PRIMARY KEY column only becomes an integer primary key if the
* declared type name is exactly "INTEGER".
*
* [source: https://www.sqlite.org/lang_createtable.html#rowid]
*/
if (primarykeycolumns.size()==1)
{
int i = names.indexOf(primarykeycolumns.get(0));
info("Primary key column :: " + i);
if(i >= 0 && sqltypes.get(i).toUpperCase().equals("INTEGER"))
{
if(!constraints.get(i).toUpperCase().contains("DESC"))
{
info("Attention! integer primary key: " + names.get(i));
/* Note: this column has the columntype "00" */
rowidcolumn = names.get(i);
pattern.change2RowID(i);
}
}
}
setHpattern(pattern);
/* create a table fingerprint for later search */
for (String columnType : getColumntypes()) {
size++;
regex += getColumn(columnType, false);
}
}
public void setVirtual(boolean val)
{
virtual = val;
}
public boolean isVirtual()
{
return virtual;
}
/**
* case 1: Returns the regex for a regular component header including the Header
* length byte. This pattern should match all records with a complete header.
*
* [headerlength] col(1) col(2) col(3) ... col(4)
*
* @return regex
*/
public Pattern getStandardPattern() {
return Pattern.compile((Auxiliary.byteToHex((byte) (size + 1)) + regex).trim());
}
/**
* case 2: Returns the regex header without header length byte but with all
* serialtypes. This pattern should match all records startRegion a component, where parts
* of the header information has been overwritten.
*
* @return regex
*/
public Pattern getPatternWithoutHeaderLength() {
return Pattern.compile(regex.trim());
}
/**
* case 3: Returns the regex header without header length byte and first column.
* This pattern should match all records startRegion a component, where parts of the header
* has been overwritten, because it has been deleted.
*
* col(2) col(3) col(4) ... col(n)
*
* @return regex
*/
public Pattern getPatternWithoutFirstCol() {
return Pattern.compile(getPattern(1, size, false));
}
/**
* Returns a regex for only some serialtypes of the component.
*
* @param startcolumn the first column to be included in the regex
* @param endcolumn the last column to be included in the regex
* @param multicol true, if the regex should match multiple columns
* @return regex
*/
public String getPattern(int startcolumn, int endcolumn, boolean multicol) {
String pat = "";
for (int i = startcolumn; i < endcolumn; i++) {
pat += getColumn(getColumntypes().get(i), multicol);
}
return pat;
}
/**
* Returns the number of the root data page.
* @return the number of the root data page
*/
public int getRootOffset() {
return this.root;
}
/**
* Returns the component header length.
* The header begins with a single varint which determines
* the total number of bytes in the header.
* The varint value is the size of the header in bytes including
* the size varint itself.
* @return the component header length
*/
public int getLength() {
return 1 + size;
}
/**
* Return the number of columns (startRegion the component header).
* @return the number of columns
*/
public int numberofColumns() {
return getColumntypes().size();
}
public Pattern getPatternMultiCol() {
return Pattern.compile(getPattern(0, size, true));
}
/**
* Returns the regular expression for the pattern matching for
* a particular serial type.
*
* The header size varint and serial type varints will usually
* consist of a single byte. The serial type varints for large
* strings and BLOBs might extend to two or three byte varints,
* but that is the exception rather than the rule.
*
* @param serialtype the serial type
* @param multicol true if multi column
* @return the regular expression for the pattern matching
*/
private String getColumn(String serialtype, boolean multicol) {
switch (serialtype) {
case "INT":
return "0[0-6]";
case "REAL":
return "07";
case "TEXT":
if (multicol)
return "[0-9a-f][0-9a-f]{0,4}";
else
return "[0-9a-f][0-9a-f]";
case "BLOB":
if (multicol)
return "[0-9a-f][0-9a-f]{0,4}";
else
return "[0-9a-f][0-9a-f]";
default:
return "[0-9a-f][0-9a-f]";
}
}
/**
* Returns a field of regex-pattern to match the current component.
* @return field with Pattern objects
*/
public Pattern[] getRegex() {
Pattern[] headers = new Pattern[4];
headers[0] = getStandardPattern();
headers[1] = getPatternWithoutHeaderLength();
headers[2] = getPatternMultiCol();
headers[3] = getPatternWithoutFirstCol();
return headers;
}
/**
* Outputs component name and column names to the console.
*
**/
public void printTableDefinition() {
info("TABLE" + tblname);
info("COLUMNS: " + columnnames);
}
@Override
public String toString() {
Pattern[] elements = getRegex();
String output = "";
for (Pattern s : elements) {
output += s + "\n";
}
return output;
}
@Override
public boolean equals(Object o) {
return this.regex.equals(((TableDescriptor) o).regex);
}
public List getColumntypes() {
return serialtypes;
}
public void setColumntypes(List columntypes) {
this.serialtypes = columntypes;
}
public HeaderPattern getHpattern() {
return hpattern;
}
public void setHpattern(HeaderPattern hpattern) {
this.hpattern = hpattern;
}
@Override
public int compareTo(TableDescriptor o) {
return tblname.compareTo(o.tblname);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy