org.h2.bnf.context.DbContextRule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of h2-mvstore Show documentation
Show all versions of h2-mvstore Show documentation
Fork of h2database to maintain Java 8 compatibility
The newest version!
/*
* Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.bnf.context;
import java.util.HashMap;
import java.util.HashSet;
import org.h2.bnf.Bnf;
import org.h2.bnf.BnfVisitor;
import org.h2.bnf.Rule;
import org.h2.bnf.RuleElement;
import org.h2.bnf.RuleHead;
import org.h2.bnf.RuleList;
import org.h2.bnf.Sentence;
import org.h2.message.DbException;
import org.h2.util.ParserUtil;
import org.h2.util.StringUtils;
/**
* A BNF terminal rule that is linked to the database context information.
* This class is used by the H2 Console, to support auto-complete.
*/
public class DbContextRule implements Rule {
public static final int COLUMN = 0, TABLE = 1, TABLE_ALIAS = 2;
public static final int NEW_TABLE_ALIAS = 3;
public static final int COLUMN_ALIAS = 4, SCHEMA = 5, PROCEDURE = 6;
private final DbContents contents;
private final int type;
private String columnType;
/**
* BNF terminal rule Constructor
* @param contents Extract rule from this component
* @param type Rule type, one of
* {@link DbContextRule#COLUMN},
* {@link DbContextRule#TABLE},
* {@link DbContextRule#TABLE_ALIAS},
* {@link DbContextRule#NEW_TABLE_ALIAS},
* {@link DbContextRule#COLUMN_ALIAS},
* {@link DbContextRule#SCHEMA}
*/
public DbContextRule(DbContents contents, int type) {
this.contents = contents;
this.type = type;
}
/**
* @param columnType COLUMN Auto completion can be filtered by column type
*/
public void setColumnType(String columnType) {
this.columnType = columnType;
}
@Override
public void setLinks(HashMap ruleMap) {
// nothing to do
}
@Override
public void accept(BnfVisitor visitor) {
// nothing to do
}
@Override
public boolean autoComplete(Sentence sentence) {
String query = sentence.getQuery(), s = query;
String up = sentence.getQueryUpper();
switch (type) {
case SCHEMA: {
DbSchema[] schemas = contents.getSchemas();
String best = null;
DbSchema bestSchema = null;
for (DbSchema schema: schemas) {
String name = StringUtils.toUpperEnglish(schema.name);
if (up.startsWith(name)) {
if (best == null || name.length() > best.length()) {
best = name;
bestSchema = schema;
}
} else if (s.length() == 0 || name.startsWith(up)) {
if (s.length() < name.length()) {
sentence.add(name, name.substring(s.length()), type);
sentence.add(schema.quotedName + ".",
schema.quotedName.substring(s.length()) + ".",
Sentence.CONTEXT);
}
}
}
if (best != null) {
sentence.setLastMatchedSchema(bestSchema);
s = s.substring(best.length());
}
break;
}
case TABLE: {
DbSchema schema = sentence.getLastMatchedSchema();
if (schema == null) {
schema = contents.getDefaultSchema();
}
DbTableOrView[] tables = schema.getTables();
String best = null;
DbTableOrView bestTable = null;
for (DbTableOrView table : tables) {
String compare = up;
String name = StringUtils.toUpperEnglish(table.getName());
if (table.getQuotedName().length() > name.length()) {
name = table.getQuotedName();
compare = query;
}
if (compare.startsWith(name)) {
if (best == null || name.length() > best.length()) {
best = name;
bestTable = table;
}
} else if (s.length() == 0 || name.startsWith(compare)) {
if (s.length() < name.length()) {
sentence.add(table.getQuotedName(),
table.getQuotedName().substring(s.length()),
Sentence.CONTEXT);
}
}
}
if (best != null) {
sentence.setLastMatchedTable(bestTable);
sentence.addTable(bestTable);
s = s.substring(best.length());
}
break;
}
case NEW_TABLE_ALIAS:
s = autoCompleteTableAlias(sentence, true);
break;
case TABLE_ALIAS:
s = autoCompleteTableAlias(sentence, false);
break;
case COLUMN_ALIAS: {
int i = 0;
if (query.indexOf(' ') < 0) {
break;
}
for (; i < up.length(); i++) {
char ch = up.charAt(i);
if (ch != '_' && !Character.isLetterOrDigit(ch)) {
break;
}
}
if (i == 0) {
break;
}
String alias = up.substring(0, i);
if (ParserUtil.isKeyword(alias, false)) {
break;
}
s = s.substring(alias.length());
break;
}
case COLUMN: {
HashSet set = sentence.getTables();
String best = null;
DbTableOrView last = sentence.getLastMatchedTable();
if (last != null && last.getColumns() != null) {
for (DbColumn column : last.getColumns()) {
String compare = up;
String name = StringUtils.toUpperEnglish(column.getName());
if (column.getQuotedName().length() > name.length()) {
name = column.getQuotedName();
compare = query;
}
if (compare.startsWith(name) && testColumnType(column)) {
String b = s.substring(name.length());
if (best == null || b.length() < best.length()) {
best = b;
} else if (s.length() == 0 || name.startsWith(compare)) {
if (s.length() < name.length()) {
sentence.add(column.getName(),
column.getName().substring(s.length()),
Sentence.CONTEXT);
}
}
}
}
}
for (DbSchema schema : contents.getSchemas()) {
for (DbTableOrView table : schema.getTables()) {
if (table != last && set != null && !set.contains(table)) {
continue;
}
if (table == null || table.getColumns() == null) {
continue;
}
for (DbColumn column : table.getColumns()) {
String name = StringUtils.toUpperEnglish(column
.getName());
if (testColumnType(column)) {
if (up.startsWith(name)) {
String b = s.substring(name.length());
if (best == null || b.length() < best.length()) {
best = b;
}
} else if (s.length() == 0 || name.startsWith(up)) {
if (s.length() < name.length()) {
sentence.add(column.getName(),
column.getName().substring(s.length()),
Sentence.CONTEXT);
}
}
}
}
}
}
if (best != null) {
s = best;
}
break;
}
case PROCEDURE:
autoCompleteProcedure(sentence);
break;
default:
throw DbException.getInternalError("type=" + type);
}
if (!s.equals(query)) {
while (Bnf.startWithSpace(s)) {
s = s.substring(1);
}
sentence.setQuery(s);
return true;
}
return false;
}
private boolean testColumnType(DbColumn column) {
if (columnType == null) {
return true;
}
String type = column.getDataType();
if (columnType.contains("CHAR") || columnType.contains("CLOB")) {
return type.contains("CHAR") || type.contains("CLOB");
}
if (columnType.contains("BINARY") || columnType.contains("BLOB")) {
return type.contains("BINARY") || type.contains("BLOB");
}
return type.contains(columnType);
}
private void autoCompleteProcedure(Sentence sentence) {
DbSchema schema = sentence.getLastMatchedSchema();
if (schema == null) {
schema = contents.getDefaultSchema();
}
String incompleteSentence = sentence.getQueryUpper();
String incompleteFunctionName = incompleteSentence;
int bracketIndex = incompleteSentence.indexOf('(');
if (bracketIndex != -1) {
incompleteFunctionName = StringUtils.trimSubstring(incompleteSentence, 0, bracketIndex);
}
// Common elements
RuleElement openBracket = new RuleElement("(", "Function");
RuleElement closeBracket = new RuleElement(")", "Function");
RuleElement comma = new RuleElement(",", "Function");
// Fetch all elements
for (DbProcedure procedure : schema.getProcedures()) {
final String procName = procedure.getName();
if (procName.startsWith(incompleteFunctionName)) {
// That's it, build a RuleList from this function
RuleElement procedureElement = new RuleElement(procName,
"Function");
RuleList rl = new RuleList(procedureElement, openBracket, false);
// Go further only if the user use open bracket
if (incompleteSentence.contains("(")) {
for (DbColumn parameter : procedure.getParameters()) {
if (parameter.getPosition() > 1) {
rl = new RuleList(rl, comma, false);
}
DbContextRule columnRule = new DbContextRule(contents,
COLUMN);
String parameterType = parameter.getDataType();
// Remove precision
if (parameterType.contains("(")) {
parameterType = parameterType.substring(0,
parameterType.indexOf('('));
}
columnRule.setColumnType(parameterType);
rl = new RuleList(rl, columnRule, false);
}
rl = new RuleList(rl, closeBracket , false);
}
rl.autoComplete(sentence);
}
}
}
private static String autoCompleteTableAlias(Sentence sentence,
boolean newAlias) {
String s = sentence.getQuery();
String up = sentence.getQueryUpper();
int i = 0;
for (; i < up.length(); i++) {
char ch = up.charAt(i);
if (ch != '_' && !Character.isLetterOrDigit(ch)) {
break;
}
}
if (i == 0) {
return s;
}
String alias = up.substring(0, i);
if ("SET".equals(alias) || ParserUtil.isKeyword(alias, false)) {
return s;
}
if (newAlias) {
sentence.addAlias(alias, sentence.getLastTable());
}
HashMap map = sentence.getAliases();
if ((map != null && map.containsKey(alias)) ||
(sentence.getLastTable() == null)) {
if (newAlias && s.length() == alias.length()) {
return s;
}
s = s.substring(alias.length());
if (s.length() == 0) {
sentence.add(alias + ".", ".", Sentence.CONTEXT);
}
return s;
}
HashSet tables = sentence.getTables();
if (tables != null) {
String best = null;
for (DbTableOrView table : tables) {
String tableName =
StringUtils.toUpperEnglish(table.getName());
if (alias.startsWith(tableName) &&
(best == null || tableName.length() > best.length())) {
sentence.setLastMatchedTable(table);
best = tableName;
} else if (s.length() == 0 || tableName.startsWith(alias)) {
sentence.add(tableName + ".",
tableName.substring(s.length()) + ".",
Sentence.CONTEXT);
}
}
if (best != null) {
s = s.substring(best.length());
if (s.length() == 0) {
sentence.add(alias + ".", ".", Sentence.CONTEXT);
}
return s;
}
}
return s;
}
}