Files
sqlitebrowser/src/sqlitetypes.cpp
2017-01-23 13:36:31 +01:00

1136 lines
33 KiB
C++

#include "sqlitetypes.h"
#include "grammar/Sqlite3Lexer.hpp"
#include "grammar/Sqlite3Parser.hpp"
#include <sstream>
#include <QDebug>
namespace sqlb {
QStringList Field::Datatypes = QStringList() << "INTEGER" << "TEXT" << "BLOB" << "REAL" << "NUMERIC";
QString escapeIdentifier(QString id)
{
return '`' + id.replace('`', "``") + '`';
}
QStringList fieldVectorToFieldNames(const FieldVector& vector)
{
QStringList result;
foreach(const FieldPtr& field, vector)
result.append(escapeIdentifier(field->name()));
return result;
}
/**
* @brief The CreateTableWalker class
* Goes trough the createtable AST and returns
* Table object.
*/
class CreateTableWalker
{
public:
explicit CreateTableWalker(antlr::RefAST r)
: m_root(r)
{}
TablePtr table();
private:
void parsecolumn(Table* table, antlr::RefAST c);
private:
antlr::RefAST m_root;
};
/**
* @brief The CreateIndexWalker class
* Goes trough the createtable AST and returns
* Index object.
*/
class CreateIndexWalker
{
public:
explicit CreateIndexWalker(antlr::RefAST r)
: m_root(r)
{}
IndexPtr index();
private:
void parsecolumn(Index* index, antlr::RefAST c);
private:
antlr::RefAST m_root;
};
ObjectPtr Object::parseSQL(Object::ObjectTypes type, const QString& sSQL)
{
// Parse SQL statement according to type
ObjectPtr result;
switch(type)
{
case Object::ObjectTypes::Table:
result = Table::parseSQL(sSQL);
break;
case Object::ObjectTypes::Index:
result = Index::parseSQL(sSQL);
break;
default:
return ObjectPtr(nullptr);
}
// Strore the original SQL statement and return the result
result->setOriginalSql(sSQL);
return result;
}
bool ForeignKeyClause::isSet() const
{
return m_override.size() || m_table.size();
}
QString ForeignKeyClause::toString() const
{
if(!isSet())
return QString();
if(m_override.size())
return m_override;
QString result = escapeIdentifier(m_table);
if(m_columns.size())
{
result += "(";
foreach(const QString& column, m_columns)
result += escapeIdentifier(column) + ',';
result.chop(1); // Remove last comma
result += ")";
}
if(m_constraint.size())
result += " " + m_constraint;
return result;
}
void ForeignKeyClause::setFromString(const QString& fk)
{
m_override = fk;
}
QString ForeignKeyClause::toSql(const FieldVector& applyOn) const
{
QString result;
if(!m_name.isNull())
result += QString("CONSTRAINT %1 ").arg(escapeIdentifier(m_name));
result += QString("FOREIGN KEY(%1) REFERENCES %2").arg(fieldVectorToFieldNames(applyOn).join(",")).arg(this->toString());
return result;
}
QString UniqueConstraint::toSql(const FieldVector& applyOn) const
{
QString result;
if(!m_name.isNull())
result += QString("CONSTRAINT %1 ").arg(escapeIdentifier(m_name));
result += QString("UNIQUE(%1)").arg(fieldVectorToFieldNames(applyOn).join(","));
return result;
}
QString PrimaryKeyConstraint::toSql(const FieldVector& applyOn) const
{
QString result;
if(!m_name.isNull())
result += QString("CONSTRAINT %1 ").arg(escapeIdentifier(m_name));
result += QString("PRIMARY KEY(%1)").arg(fieldVectorToFieldNames(applyOn).join(","));
return result;
}
QString Field::toString(const QString& indent, const QString& sep) const
{
QString str = indent + escapeIdentifier(m_name) + sep + m_type;
if(m_notnull)
str += " NOT NULL";
if(!m_defaultvalue.isEmpty())
str += QString(" DEFAULT %1").arg(m_defaultvalue);
if(!m_check.isEmpty())
str += " CHECK(" + m_check + ")";
if(m_autoincrement)
str += " PRIMARY KEY AUTOINCREMENT";
if(m_unique)
str += " UNIQUE";
return str;
}
bool Field::isText() const
{
QString norm = m_type.trimmed().toLower();
return norm.startsWith("character")
|| norm.startsWith("varchar")
|| norm.startsWith("varying character")
|| norm.startsWith("nchar")
|| norm.startsWith("native character")
|| norm.startsWith("nvarchar")
|| norm == "text"
|| norm == "clob";
}
bool Field::isInteger() const
{
QString norm = m_type.trimmed().toLower();
return norm == "int"
|| norm == "integer"
|| norm == "tinyint"
|| norm == "smallint"
|| norm == "mediumint"
|| norm == "bigint"
|| norm == "unsigned big int"
|| norm == "int2"
|| norm == "int8";
}
void Table::clear()
{
m_rowidColumn = "_rowid_";
m_fields.clear();
m_constraints.clear();
m_virtual = QString();
m_temporary = false;
}
Table::~Table()
{
clear();
}
void Table::addField(const FieldPtr& f)
{
m_fields.append(FieldPtr(f));
}
bool Table::removeField(const QString& sFieldName)
{
int index = findField(sFieldName);
if( index != -1)
{
m_fields.remove(index);
return true;
}
return false;
}
void Table::setFields(const FieldVector &fields)
{
clear();
m_fields = fields;
}
void Table::setField(int index, FieldPtr f)
{
FieldPtr oldField = m_fields[index];
m_fields[index] = f;
// Update all constraints. If an existing field is updated but was used in a constraint, the pointers in the constraint key needs to be updated
// to the new field, too.
if(oldField)
{
ConstraintMap::iterator it = m_constraints.begin();
while(it != m_constraints.end())
{
// Loop through all fields mentioned in a foreign key
FieldVector fields = it.key();
bool modified = false;
for(int i=0;i<fields.size();++i)
{
// If the field that is being modified is in there update it to the new field and set a flag that something has changed.
// This is used below to know when to update the map key
if(fields[i] == oldField)
{
fields[i] = f;
modified = true;
}
}
if(modified)
{
// When we need to update the map key, we insert a new constraint using the updated field vector and the old
// constraint information, and delete the old one afterwards
m_constraints.insert(fields, it.value());
it = m_constraints.erase(it);
} else {
++it;
}
}
}
}
int Table::findField(const QString &sname) const
{
for(int i = 0; i < m_fields.count(); ++i)
{
if(m_fields.at(i)->name().compare(sname, Qt::CaseInsensitive) == 0)
return i;
}
return -1;
}
int Table::findPk() const
{
// TODO This is a stupid function (and always was) which should be fixed/improved
FieldVector pk = primaryKey();
if(pk.empty())
return -1;
else
return findField(pk.at(0)->name());
}
QStringList Table::fieldList() const
{
QStringList sl;
foreach(const FieldPtr& f, m_fields) {
sl << f->toString();
}
return sl;
}
QStringList Table::fieldNames() const
{
QStringList sl;
foreach(FieldPtr f, m_fields)
sl << f->name();
return sl;
}
bool Table::hasAutoIncrement() const
{
foreach(FieldPtr f, m_fields) {
if(f->autoIncrement())
return true;
}
return false;
}
ObjectPtr Table::parseSQL(const QString &sSQL)
{
std::stringstream s;
s << sSQL.toStdString();
Sqlite3Lexer lex(s);
Sqlite3Parser parser(lex);
antlr::ASTFactory ast_factory;
parser.initializeASTFactory(ast_factory);
parser.setASTFactory(&ast_factory);
try
{
parser.createtable();
CreateTableWalker ctw(parser.getAST());
return ctw.table();
}
catch(antlr::ANTLRException& ex)
{
qCritical() << "Sqlite parse error: " << QString::fromStdString(ex.toString()) << "(" << sSQL << ")";
}
catch(...)
{
qCritical() << "Sqlite parse error: " << sSQL; //TODO
}
return TablePtr(new Table(""));
}
QString Table::sql() const
{
// Special handling for virtual tables: just build an easy create statement and copy the using part in there
if(isVirtual())
return QString("CREATE VIRTUAL TABLE %1 USING %2;").arg(escapeIdentifier(m_name)).arg(m_virtual);
// This is a normal table, not a virtual one
QString sql = QString("CREATE %1TABLE %2 (\n").arg(m_temporary ? QString("TEMPORARY ") : QString("")).arg(escapeIdentifier(m_name));
sql += fieldList().join(",\n");
// Constraints
ConstraintMap::const_iterator it = m_constraints.constBegin();
bool autoincrement = hasAutoIncrement();
while(it != m_constraints.constEnd())
{
if((!autoincrement || it.value()->type() != Constraint::PrimaryKeyConstraintType) && !it.key().isEmpty())
{
sql += QString(",\n\t");
sql += it.value()->toSql(it.key());
}
++it;
}
sql += "\n)";
// without rowid
if(isWithoutRowidTable())
sql += " WITHOUT ROWID";
return sql + ";";
}
void Table::addConstraint(FieldVector fields, ConstraintPtr constraint)
{
m_constraints.insert(fields, constraint);
}
void Table::setConstraint(FieldVector fields, ConstraintPtr constraint)
{
// Delete any old constraints of this type for these fields
removeConstraints(fields, constraint->type());
// Add the new constraint to the table, effectively overwriting all old constraints for that fields/type combination
addConstraint(fields, constraint);
}
void Table::removeConstraints(FieldVector fields, Constraint::ConstraintTypes type)
{
QList<ConstraintPtr> list = constraints(fields, type);
for (ConstraintPtr c : list) {
m_constraints.remove(fields, c);
}
}
ConstraintPtr Table::constraint(FieldVector fields, Constraint::ConstraintTypes type) const
{
QList<ConstraintPtr> list = constraints(fields, type);
if(list.size())
return list.at(0);
else
return ConstraintPtr(nullptr);
}
QList<ConstraintPtr> Table::constraints(FieldVector fields, Constraint::ConstraintTypes type) const
{
QList<ConstraintPtr> clist;
if(fields.isEmpty())
clist = m_constraints.values();
else
clist = m_constraints.values(fields);
if(type == Constraint::NoType)
{
return clist;
} else {
QList<ConstraintPtr> clist_typed;
foreach(const ConstraintPtr& ptr, clist)
{
if(ptr->type() == type)
clist_typed.push_back(ptr);
}
return clist_typed;
}
}
void Table::setConstraints(const ConstraintMap& constraints)
{
m_constraints = constraints;
}
FieldVector& Table::primaryKeyRef()
{
return const_cast<FieldVector&>(static_cast<const Table*>(this)->primaryKey());
}
const FieldVector& Table::primaryKey() const
{
auto it = m_constraints.constBegin();
while(it != m_constraints.constEnd())
{
if(it.value()->type() == Constraint::PrimaryKeyConstraintType)
return it.key();
++it;
}
static FieldVector emptyFieldVector;
return emptyFieldVector;
}
namespace
{
QString identifier(antlr::RefAST ident)
{
QString sident = ident->getText().c_str();
if(ident->getType() == sqlite3TokenTypes::QUOTEDID ||
ident->getType() == Sqlite3Lexer::QUOTEDLITERAL ||
ident->getType() == sqlite3TokenTypes::STRINGLITERAL)
{
// Remember the way the identifier is quoted
QChar quoteChar = sident.at(0);
// Remove first and final character, i.e. the quotes
sident.remove(0, 1);
sident.chop(1);
// Replace all remaining occurences of two succeeding quote characters and replace them
// by a single instance. This is done because two quotes can be used as a means of escaping
// the quote character, thus only the visual representation has its two quotes, the actual
// name contains only one.
sident.replace(QString(quoteChar) + quoteChar, quoteChar);
}
return sident;
}
QString concatTextAST(antlr::RefAST t, bool withspace = false)
{
QStringList stext;
while(t != antlr::nullAST)
{
// When this is called for a KEYWORDASTABLENAME token, we must take the child's content to get the actual value
// instead of 'KEYWORDASTABLENAME' as a string. The same applies for KEYWORDASCOLUMNNAME tokens.
if(t != antlr::nullAST && (t->getType() == sqlite3TokenTypes::KEYWORDASTABLENAME || t->getType() == sqlite3TokenTypes::KEYWORDASCOLUMNNAME))
stext.append(t->getFirstChild()->getText().c_str());
else
stext.append(t->getText().c_str());
t = t->getNextSibling();
}
return stext.join(withspace ? " " : "");
}
}
namespace {
QString tablename(const antlr::RefAST& n)
{
if(n->getType() == sqlite3TokenTypes::KEYWORDASTABLENAME)
return concatTextAST(n->getFirstChild());
else
return identifier(n);
}
QString columnname(const antlr::RefAST& n)
{
if(n->getType() == sqlite3TokenTypes::KEYWORDASCOLUMNNAME)
return concatTextAST(n->getFirstChild());
else
return identifier(n);
}
}
TablePtr CreateTableWalker::table()
{
Table* tab = new Table("");
tab->setFullyParsed(true);
if( m_root ) //CREATE TABLE
{
antlr::RefAST s = m_root->getFirstChild();
// If the primary tree isn't filled, this isn't a normal CREATE TABLE statement. Switch to the next alternative tree.
if(s == 0)
s = m_root->getNextSibling();
// Skip to table name
bool is_virtual_table = false;
while(s->getType() != Sqlite3Lexer::ID &&
s->getType() != Sqlite3Lexer::QUOTEDID &&
s->getType() != Sqlite3Lexer::QUOTEDLITERAL &&
s->getType() != Sqlite3Lexer::STRINGLITERAL &&
s->getType() != sqlite3TokenTypes::KEYWORDASTABLENAME)
{
// Is this one of these virtual tables?
if(s->getType() == Sqlite3Lexer::VIRTUAL)
is_virtual_table = true;
s = s->getNextSibling();
}
// Extract and set table name
tab->setName(tablename(s));
// Special handling for virtual tables. If this is a virtual table, extract the USING part and skip all the
// rest of this function because virtual tables don't have column definitons
if(is_virtual_table)
{
s = s->getNextSibling(); // USING
s = s->getNextSibling(); // module name
tab->setVirtualUsing(concatTextAST(s, true));
tab->setFullyParsed(false);
return TablePtr(tab);
}
// This is a normal table, not a virtual one
s = s->getNextSibling(); // LPAREN
s = s->getNextSibling(); // first column name
antlr::RefAST column = s;
// loop columndefs
while(column != antlr::nullAST && column->getType() == sqlite3TokenTypes::COLUMNDEF)
{
parsecolumn(tab, column->getFirstChild());
column = column->getNextSibling(); //COMMA or RPAREN
column = column->getNextSibling(); //null or tableconstraint
s = s->getNextSibling(); // COLUMNDEF
s = s->getNextSibling(); // COMMA or RPAREN
}
// now we are finished or it is a tableconstraint
while(s != antlr::nullAST)
{
// Is this a 'without rowid' definiton?
if(s->getType() != sqlite3TokenTypes::WITHOUT)
{
// It's not, so treat this as table constraints
antlr::RefAST tc = s->getFirstChild();
// Extract constraint name, if there is any
QString constraint_name;
if(tc->getType() == sqlite3TokenTypes::CONSTRAINT)
{
tc = tc->getNextSibling(); // CONSTRAINT
constraint_name = identifier(tc);
tc = tc->getNextSibling(); // identifier
}
switch(tc->getType())
{
case sqlite3TokenTypes::PRIMARY:
{
PrimaryKeyConstraint* pk = new PrimaryKeyConstraint;
pk->setName(constraint_name);
tc = tc->getNextSibling()->getNextSibling(); // skip primary and key
tc = tc->getNextSibling(); // skip LPAREN
FieldVector fields;
do
{
QString col = columnname(tc);
FieldPtr field = tab->field(tab->findField(col));
fields.push_back(field);
tc = tc->getNextSibling();
if(tc != antlr::nullAST
&& (tc->getType() == sqlite3TokenTypes::ASC
|| tc->getType() == sqlite3TokenTypes::DESC))
{
// TODO save ASC / DESC information?
tab->setFullyParsed(false);
tc = tc->getNextSibling();
}
if(tc != antlr::nullAST && tc->getType() == sqlite3TokenTypes::AUTOINCREMENT)
{
field->setAutoIncrement(true);
tc = tc->getNextSibling();
}
while(tc != antlr::nullAST && tc->getType() == sqlite3TokenTypes::COMMA)
{
tc = tc->getNextSibling(); // skip ident and comma
}
} while(tc != antlr::nullAST && tc->getType() != sqlite3TokenTypes::RPAREN);
tab->addConstraint(fields, ConstraintPtr(pk));
}
break;
case sqlite3TokenTypes::UNIQUE:
{
UniqueConstraint* unique = new UniqueConstraint;
unique->setName(constraint_name);
tc = tc->getNextSibling(); // skip UNIQUE
tc = tc->getNextSibling(); // skip LPAREN
FieldVector fields;
do
{
QString col = columnname(tc);
FieldPtr field = tab->field(tab->findField(col));
fields.push_back(field);
tc = tc->getNextSibling();
if(tc != antlr::nullAST
&& (tc->getType() == sqlite3TokenTypes::ASC
|| tc->getType() == sqlite3TokenTypes::DESC))
{
// TODO save ASC / DESC information?
tab->setFullyParsed(false);
tc = tc->getNextSibling();
}
while(tc != antlr::nullAST && tc->getType() == sqlite3TokenTypes::COMMA)
{
tc = tc->getNextSibling(); // skip ident and comma
}
} while(tc != antlr::nullAST && tc->getType() != sqlite3TokenTypes::RPAREN);
if(fields.size() == 1 && constraint_name.isEmpty())
{
fields[0]->setUnique(true);
delete unique;
} else {
tab->addConstraint(fields, ConstraintPtr(unique));
}
}
break;
case sqlite3TokenTypes::FOREIGN:
{
ForeignKeyClause* fk = new ForeignKeyClause;
fk->setName(constraint_name);
tc = tc->getNextSibling(); // FOREIGN
tc = tc->getNextSibling(); // KEY
tc = tc->getNextSibling(); // LPAREN
FieldVector fields;
do
{
QString col = columnname(tc);
FieldPtr field = tab->field(tab->findField(col));
fields.push_back(field);
tc = tc->getNextSibling();
while(tc != antlr::nullAST && tc->getType() == sqlite3TokenTypes::COMMA)
tc = tc->getNextSibling(); // skip ident and comma
} while(tc != antlr::nullAST && tc->getType() != sqlite3TokenTypes::RPAREN);
tc = tc->getNextSibling();
tc = tc->getNextSibling(); // REFERENCES
fk->setTable(identifier(tc));
tc = tc->getNextSibling(); // identifier
if(tc != antlr::nullAST && tc->getType() == sqlite3TokenTypes::LPAREN)
{
tc = tc->getNextSibling(); // LPAREN
QStringList fk_cols;
while(tc != antlr::nullAST && tc->getType() != sqlite3TokenTypes::RPAREN)
{
if(tc->getType() != sqlite3TokenTypes::COMMA)
fk_cols.push_back(identifier(tc));
tc = tc->getNextSibling();
}
fk->setColumns(fk_cols);
tc = tc->getNextSibling(); // RPAREN
}
fk->setConstraint(concatTextAST(tc, true));
tab->addConstraint(fields, ConstraintPtr(fk));
}
break;
default:
{
tab->setFullyParsed(false);
}
break;
}
s = s->getNextSibling(); //COMMA or RPAREN
if(s->getType() == sqlite3TokenTypes::COMMA || s->getType() == sqlite3TokenTypes::RPAREN)
s = s->getNextSibling();
} else {
// It is
s = s->getNextSibling(); // WITHOUT
s = s->getNextSibling(); // ROWID
tab->setRowidColumn(tab->fields().at(tab->findPk())->name());
}
}
}
return TablePtr(tab);
}
void CreateTableWalker::parsecolumn(Table* table, antlr::RefAST c)
{
QString colname;
QString type = "TEXT";
bool autoincrement = false;
bool notnull = false;
bool unique = false;
QString defaultvalue;
QString check;
sqlb::PrimaryKeyConstraint* primaryKey = 0;
sqlb::ForeignKeyClause* foreignKey = 0;
colname = columnname(c);
c = c->getNextSibling(); //type?
if(c != antlr::nullAST && c->getType() == sqlite3TokenTypes::TYPE_NAME)
{
type = concatTextAST(c->getFirstChild(), true);
c = c->getNextSibling();
}
// finished with type parsing
// now columnconstraints
while(c != antlr::nullAST)
{
antlr::RefAST con = c->getFirstChild();
// Extract constraint name, if there is any
QString constraint_name;
if(con->getType() == sqlite3TokenTypes::CONSTRAINT)
{
con = con->getNextSibling(); // CONSTRAINT
constraint_name = identifier(con);
con = con->getNextSibling(); // identifier
}
switch(con->getType())
{
case sqlite3TokenTypes::PRIMARY:
{
primaryKey = new PrimaryKeyConstraint;
primaryKey->setName(constraint_name);
con = con->getNextSibling()->getNextSibling(); // skip KEY
if(con != antlr::nullAST && (con->getType() == sqlite3TokenTypes::ASC
|| con->getType() == sqlite3TokenTypes::DESC))
{
table->setFullyParsed(false);
con = con->getNextSibling(); //skip
}
if(con != antlr::nullAST && con->getType() == sqlite3TokenTypes::AUTOINCREMENT)
autoincrement = true;
}
break;
case sqlite3TokenTypes::NOT:
{
// TODO Support constraint names here
if(!constraint_name.isEmpty())
table->setFullyParsed(false);
notnull = true;
}
break;
case sqlite3TokenTypes::NULL_T:
{
notnull = false;
}
break;
case sqlite3TokenTypes::CHECK:
{
// TODO Support constraint names here
if(!constraint_name.isEmpty())
table->setFullyParsed(false);
con = con->getNextSibling(); //LPAREN
check = concatTextAST(con, true);
// remove parenthesis
check.remove(check.length()-1, 1);
check.remove(0,1);
check = check.trimmed();
}
break;
case sqlite3TokenTypes::DEFAULT:
{
// TODO Support constraint names here
if(!constraint_name.isEmpty())
table->setFullyParsed(false);
con = con->getNextSibling(); //SIGNEDNUMBER,STRING,LPAREN
defaultvalue = concatTextAST(con);
}
break;
case sqlite3TokenTypes::UNIQUE:
{
// TODO Support constraint names here
if(!constraint_name.isEmpty())
table->setFullyParsed(false);
unique = true;
}
break;
case sqlite3TokenTypes::REFERENCES:
{
con = con->getNextSibling(); // REFERENCES
foreignKey = new ForeignKeyClause;
foreignKey->setTable(identifier(con));
foreignKey->setName(constraint_name);
con = con->getNextSibling(); // identifier
if(con != antlr::nullAST && con->getType() == sqlite3TokenTypes::LPAREN)
{
con = con->getNextSibling(); // LPAREN
QStringList fk_cols;
while(con != antlr::nullAST && con->getType() != sqlite3TokenTypes::RPAREN)
{
if(con->getType() != sqlite3TokenTypes::COMMA)
fk_cols.push_back(identifier(con));
con = con->getNextSibling();
}
foreignKey->setColumns(fk_cols);
con = con->getNextSibling(); // RPAREN
}
foreignKey->setConstraint(concatTextAST(con, true));
}
break;
default:
{
table->setFullyParsed(false);
}
break;
}
c = c->getNextSibling();
}
FieldPtr f = FieldPtr(new Field(colname, type, notnull, defaultvalue, check, unique));
f->setAutoIncrement(autoincrement);
table->addField(f);
if(foreignKey)
table->addConstraint({f}, ConstraintPtr(foreignKey));
if(primaryKey)
{
FieldVector v;
if(table->constraint(v, Constraint::PrimaryKeyConstraintType))
table->primaryKeyRef().push_back(f);
else
table->addConstraint({f}, ConstraintPtr(primaryKey));
}
}
QString IndexedColumn::toString(const QString& indent, const QString& sep) const
{
return indent + (m_isExpression ? m_name : escapeIdentifier(m_name)) + sep + m_order;
}
Index::~Index()
{
clear();
}
void Index::clear()
{
m_name.clear();
m_unique = false;
m_table.clear();
m_whereExpr.clear();
m_columns.clear();
}
bool Index::removeColumn(const QString& name)
{
int index = findColumn(name);
if(index != -1)
{
m_columns.remove(index);
return true;
}
return false;
}
void Index::setColumns(const IndexedColumnVector& columns)
{
clear();
m_columns = columns;
}
int Index::findColumn(const QString& name) const
{
for(int i=0;i<m_columns.count();++i)
{
if(m_columns.at(i)->name().compare(name, Qt::CaseInsensitive) == 0)
return i;
}
return -1;
}
QStringList Index::columnSqlList() const
{
QStringList sl;
foreach(const IndexedColumnPtr& c, m_columns)
sl << c->toString();
return sl;
}
QString Index::sql() const
{
// Start CREATE (UNIQUE) INDEX statement
QString sql = QString("CREATE %1INDEX %2 ON %3 (\n")
.arg(m_unique ? QString("UNIQUE ") : QString(""))
.arg(escapeIdentifier(m_name))
.arg(escapeIdentifier(m_table));
// Add column list
sql += columnSqlList().join(",\n");
// Add partial index bit
sql += QString("\n)");
if(!m_whereExpr.isEmpty())
sql += QString(" WHERE ") + m_whereExpr;
return sql + ";";
}
ObjectPtr Index::parseSQL(const QString& sSQL)
{
std::stringstream s;
s << sSQL.toStdString();
Sqlite3Lexer lex(s);
Sqlite3Parser parser(lex);
antlr::ASTFactory ast_factory;
parser.initializeASTFactory(ast_factory);
parser.setASTFactory(&ast_factory);
try
{
parser.createindex();
CreateIndexWalker ctw(parser.getAST());
return ctw.index();
}
catch(antlr::ANTLRException& ex)
{
qCritical() << "Sqlite parse error: " << QString::fromStdString(ex.toString()) << "(" << sSQL << ")";
}
catch(...)
{
qCritical() << "Sqlite parse error: " << sSQL; //TODO
}
return IndexPtr(new Index(""));
}
IndexPtr CreateIndexWalker::index()
{
Index* index = new Index("");
index->setFullyParsed(true);
if(m_root) // CREATE INDEX
{
antlr::RefAST s = m_root->getFirstChild();
// Skip to index name
while(s->getType() != Sqlite3Lexer::ID &&
s->getType() != Sqlite3Lexer::QUOTEDID &&
s->getType() != Sqlite3Lexer::QUOTEDLITERAL &&
s->getType() != Sqlite3Lexer::STRINGLITERAL &&
s->getType() != sqlite3TokenTypes::KEYWORDASTABLENAME)
{
// Is this a unique index?
if(s->getType() == Sqlite3Lexer::UNIQUE)
index->setUnique(true);
s = s->getNextSibling();
}
// Extract and set index name
index->setName(tablename(s));
// Get table name
s = s->getNextSibling(); // ON
s = s->getNextSibling(); // table name
index->setTable(tablename(s));
s = s->getNextSibling(); // LPAREN
s = s->getNextSibling(); // first column name
antlr::RefAST column = s;
// loop columndefs
while(column != antlr::nullAST && column->getType() == sqlite3TokenTypes::INDEXCOLUMN)
{
parsecolumn(index, column->getFirstChild());
column = column->getNextSibling(); // COMMA or RPAREN
column = column->getNextSibling(); // null or WHERE
s = s->getNextSibling(); // COLUMNDEF
s = s->getNextSibling(); // COMMA or RPAREN
}
// Now we are finished or it is a partial index
if(s != antlr::nullAST)
{
// This should be a 'where' then
if(s->getType() != sqlite3TokenTypes::WHERE)
{
// It is something else
index->setFullyParsed(false);
} else {
s = s->getNextSibling(); // expr
index->setWhereExpr(concatTextAST(s, true));
}
}
}
return IndexPtr(index);
}
void CreateIndexWalker::parsecolumn(Index* index, antlr::RefAST c)
{
QString name;
bool isExpression;
QString order;
// First count the number of nodes used for the name or the expression. We reach the end of the name nodes list when we either
// get to the end of the list, get to a COMMA or a RPAREN, or get to the COLLATE keyword or get to the ASC/DESC keywords.
// Then see how many items there are: if it's one it's a normal index column with only a column name. In this case get the identifier.
// If it's more than one item it's an expression. In this case get all the items as they are.
int number_of_name_items = 0;
antlr::RefAST n = c;
while(n != antlr::nullAST
&& n->getType() != sqlite3TokenTypes::COLLATE
&& n->getType() != sqlite3TokenTypes::ASC
&& n->getType() != sqlite3TokenTypes::DESC
&& n->getType() != sqlite3TokenTypes::COMMA
&& n->getType() != sqlite3TokenTypes::RPAREN)
{
number_of_name_items++;
n = n->getNextSibling();
}
if(number_of_name_items == 1)
{
name = identifier(c);
isExpression = false;
c = c->getNextSibling();
} else {
for(int i=0;i<number_of_name_items;i++)
{
name += c->getText().c_str() + QString(" ");
c = c->getNextSibling();
}
name.chop(1);
isExpression = true;
}
// Parse the rest of the column definition
while(c != antlr::nullAST)
{
switch(c->getType())
{
case sqlite3TokenTypes::ASC:
case sqlite3TokenTypes::DESC:
order = c->getText().c_str();
break;
default:
// TODO Add support for COLLATE
index->setFullyParsed(false);
}
c = c->getNextSibling();
}
index->addColumn(IndexedColumnPtr(new IndexedColumn(name, isExpression, order)));
}
} //namespace sqlb