/*
* Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.lealone.dbobject.table;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import org.lealone.command.Command;
import org.lealone.constant.Constants;
import org.lealone.dbobject.Constant;
import org.lealone.dbobject.DbObject;
import org.lealone.dbobject.FunctionAlias;
import org.lealone.dbobject.Right;
import org.lealone.dbobject.Role;
import org.lealone.dbobject.Schema;
import org.lealone.dbobject.SchemaObject;
import org.lealone.dbobject.Sequence;
import org.lealone.dbobject.Setting;
import org.lealone.dbobject.TriggerObject;
import org.lealone.dbobject.User;
import org.lealone.dbobject.UserAggregate;
import org.lealone.dbobject.UserDataType;
import org.lealone.dbobject.constraint.Constraint;
import org.lealone.dbobject.constraint.ConstraintCheck;
import org.lealone.dbobject.constraint.ConstraintReferential;
import org.lealone.dbobject.constraint.ConstraintUnique;
import org.lealone.dbobject.index.Index;
import org.lealone.dbobject.index.IndexType;
import org.lealone.dbobject.index.MetaIndex;
import org.lealone.dbobject.index.MultiVersionIndex;
import org.lealone.engine.Csv;
import org.lealone.engine.Database;
import org.lealone.engine.InDoubtTransaction;
import org.lealone.engine.Session;
import org.lealone.expression.ValueExpression;
import org.lealone.message.DbException;
import org.lealone.result.Row;
import org.lealone.result.SearchRow;
import org.lealone.result.SortOrder;
import org.lealone.util.MathUtils;
import org.lealone.util.New;
import org.lealone.util.StatementBuilder;
import org.lealone.util.StringUtils;
import org.lealone.util.Utils;
import org.lealone.value.CompareMode;
import org.lealone.value.DataType;
import org.lealone.value.Value;
import org.lealone.value.ValueNull;
import org.lealone.value.ValueString;
import org.lealone.value.ValueStringIgnoreCase;
/**
* This class is responsible to build the database meta data pseudo tables.
*/
public class MetaTable extends Table {
/**
* The approximate number of rows of a meta table.
*/
public static final long ROW_COUNT_APPROXIMATION = 1000;
private static final String CHARACTER_SET_NAME = "Unicode";
private static final int TABLES = 0;
private static final int COLUMNS = 1;
private static final int INDEXES = 2;
private static final int TABLE_TYPES = 3;
private static final int TYPE_INFO = 4;
private static final int CATALOGS = 5;
private static final int SETTINGS = 6;
private static final int HELP = 7;
private static final int SEQUENCES = 8;
private static final int USERS = 9;
private static final int ROLES = 10;
private static final int RIGHTS = 11;
private static final int FUNCTION_ALIASES = 12;
private static final int SCHEMATA = 13;
private static final int TABLE_PRIVILEGES = 14;
private static final int COLUMN_PRIVILEGES = 15;
private static final int COLLATIONS = 16;
private static final int VIEWS = 17;
private static final int IN_DOUBT = 18;
private static final int CROSS_REFERENCES = 19;
private static final int CONSTRAINTS = 20;
private static final int FUNCTION_COLUMNS = 21;
private static final int CONSTANTS = 22;
private static final int DOMAINS = 23;
private static final int TRIGGERS = 24;
private static final int SESSIONS = 25;
private static final int LOCKS = 26;
private static final int SESSION_STATE = 27;
private static final int META_TABLE_TYPE_COUNT = SESSION_STATE + 1;
private final int type;
private final int indexColumn;
private final MetaIndex metaIndex;
/**
* Create a new metadata table.
*
* @param schema the schema
* @param id the object id
* @param type the meta table type
*/
public MetaTable(Schema schema, int id, int type) {
// tableName will be set later
super(schema, id, null, true, true);
this.type = type;
Column[] cols;
String indexColumnName = null;
switch (type) {
case TABLES:
setObjectName("TABLES");
cols = createColumns("TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "TABLE_TYPE",
// extensions
"STORAGE_TYPE", "SQL", "REMARKS", "LAST_MODIFICATION BIGINT", "ID INT", "TYPE_NAME", "TABLE_CLASS");
indexColumnName = "TABLE_NAME";
break;
case COLUMNS:
setObjectName("COLUMNS");
cols = createColumns("TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "COLUMN_NAME", "ORDINAL_POSITION INT",
"COLUMN_DEFAULT", "IS_NULLABLE", "DATA_TYPE INT", "CHARACTER_MAXIMUM_LENGTH INT",
"CHARACTER_OCTET_LENGTH INT", "NUMERIC_PRECISION INT", "NUMERIC_PRECISION_RADIX INT", "NUMERIC_SCALE INT",
"CHARACTER_SET_NAME", "COLLATION_NAME",
// extensions
"TYPE_NAME", "NULLABLE INT", "IS_COMPUTED BIT", "SELECTIVITY INT", "CHECK_CONSTRAINT", "SEQUENCE_NAME",
"REMARKS", "SOURCE_DATA_TYPE SMALLINT");
indexColumnName = "TABLE_NAME";
break;
case INDEXES:
setObjectName("INDEXES");
cols = createColumns("TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "NON_UNIQUE BIT", "INDEX_NAME",
"ORDINAL_POSITION SMALLINT", "COLUMN_NAME", "CARDINALITY INT", "PRIMARY_KEY BIT", "INDEX_TYPE_NAME",
"IS_GENERATED BIT", "INDEX_TYPE SMALLINT", "ASC_OR_DESC", "PAGES INT", "FILTER_CONDITION", "REMARKS", "SQL",
"ID INT", "SORT_TYPE INT", "CONSTRAINT_NAME", "INDEX_CLASS");
indexColumnName = "TABLE_NAME";
break;
case TABLE_TYPES:
setObjectName("TABLE_TYPES");
cols = createColumns("TYPE");
break;
case TYPE_INFO:
setObjectName("TYPE_INFO");
cols = createColumns("TYPE_NAME", "DATA_TYPE INT", "PRECISION INT", "PREFIX", "SUFFIX", "PARAMS",
"AUTO_INCREMENT BIT", "MINIMUM_SCALE SMALLINT", "MAXIMUM_SCALE SMALLINT", "RADIX INT", "POS INT",
"CASE_SENSITIVE BIT", "NULLABLE SMALLINT", "SEARCHABLE SMALLINT");
break;
case CATALOGS:
setObjectName("CATALOGS");
cols = createColumns("CATALOG_NAME");
break;
case SETTINGS:
setObjectName("SETTINGS");
cols = createColumns("NAME", "VALUE");
break;
case HELP:
setObjectName("HELP");
cols = createColumns("ID INT", "SECTION", "TOPIC", "SYNTAX", "TEXT");
break;
case SEQUENCES:
setObjectName("SEQUENCES");
cols = createColumns("SEQUENCE_CATALOG", "SEQUENCE_SCHEMA", "SEQUENCE_NAME", "CURRENT_VALUE BIGINT",
"INCREMENT BIGINT", "IS_GENERATED BIT", "REMARKS", "CACHE BIGINT", "ID INT");
break;
case USERS:
setObjectName("USERS");
cols = createColumns("NAME", "ADMIN", "REMARKS", "ID INT");
break;
case ROLES:
setObjectName("ROLES");
cols = createColumns("NAME", "REMARKS", "ID INT");
break;
case RIGHTS:
setObjectName("RIGHTS");
cols = createColumns("GRANTEE", "GRANTEETYPE", "GRANTEDROLE", "RIGHTS", "TABLE_SCHEMA", "TABLE_NAME", "ID INT");
indexColumnName = "TABLE_NAME";
break;
case FUNCTION_ALIASES:
setObjectName("FUNCTION_ALIASES");
cols = createColumns("ALIAS_CATALOG", "ALIAS_SCHEMA", "ALIAS_NAME", "JAVA_CLASS", "JAVA_METHOD", "DATA_TYPE INT",
"TYPE_NAME", "COLUMN_COUNT INT", "RETURNS_RESULT SMALLINT", "REMARKS", "ID INT", "SOURCE");
break;
case FUNCTION_COLUMNS:
setObjectName("FUNCTION_COLUMNS");
cols = createColumns("ALIAS_CATALOG", "ALIAS_SCHEMA", "ALIAS_NAME", "JAVA_CLASS", "JAVA_METHOD", "COLUMN_COUNT INT",
"POS INT", "COLUMN_NAME", "DATA_TYPE INT", "TYPE_NAME", "PRECISION INT", "SCALE SMALLINT", "RADIX SMALLINT",
"NULLABLE SMALLINT", "COLUMN_TYPE SMALLINT", "REMARKS", "COLUMN_DEFAULT");
break;
case SCHEMATA:
setObjectName("SCHEMATA");
cols = createColumns("CATALOG_NAME", "SCHEMA_NAME", "SCHEMA_OWNER", "DEFAULT_CHARACTER_SET_NAME",
"DEFAULT_COLLATION_NAME", "IS_DEFAULT BIT", "REMARKS", "ID INT");
break;
case TABLE_PRIVILEGES:
setObjectName("TABLE_PRIVILEGES");
cols = createColumns("GRANTOR", "GRANTEE", "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "PRIVILEGE_TYPE",
"IS_GRANTABLE");
indexColumnName = "TABLE_NAME";
break;
case COLUMN_PRIVILEGES:
setObjectName("COLUMN_PRIVILEGES");
cols = createColumns("GRANTOR", "GRANTEE", "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "COLUMN_NAME",
"PRIVILEGE_TYPE", "IS_GRANTABLE");
indexColumnName = "TABLE_NAME";
break;
case COLLATIONS:
setObjectName("COLLATIONS");
cols = createColumns("NAME", "KEY");
break;
case VIEWS:
setObjectName("VIEWS");
cols = createColumns("TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "VIEW_DEFINITION", "CHECK_OPTION",
"IS_UPDATABLE", "STATUS", "REMARKS", "ID INT");
indexColumnName = "TABLE_NAME";
break;
case IN_DOUBT:
setObjectName("IN_DOUBT");
cols = createColumns("TRANSACTION", "STATE");
break;
case CROSS_REFERENCES:
setObjectName("CROSS_REFERENCES");
cols = createColumns("PKTABLE_CATALOG", "PKTABLE_SCHEMA", "PKTABLE_NAME", "PKCOLUMN_NAME", "FKTABLE_CATALOG",
"FKTABLE_SCHEMA", "FKTABLE_NAME", "FKCOLUMN_NAME", "ORDINAL_POSITION SMALLINT", "UPDATE_RULE SMALLINT",
"DELETE_RULE SMALLINT", "FK_NAME", "PK_NAME", "DEFERRABILITY SMALLINT");
indexColumnName = "PKTABLE_NAME";
break;
case CONSTRAINTS:
setObjectName("CONSTRAINTS");
cols = createColumns("CONSTRAINT_CATALOG", "CONSTRAINT_SCHEMA", "CONSTRAINT_NAME", "CONSTRAINT_TYPE",
"TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "UNIQUE_INDEX_NAME", "CHECK_EXPRESSION", "COLUMN_LIST",
"REMARKS", "SQL", "ID INT");
indexColumnName = "TABLE_NAME";
break;
case CONSTANTS:
setObjectName("CONSTANTS");
cols = createColumns("CONSTANT_CATALOG", "CONSTANT_SCHEMA", "CONSTANT_NAME", "DATA_TYPE INT", "REMARKS", "SQL",
"ID INT");
break;
case DOMAINS:
setObjectName("DOMAINS");
cols = createColumns("DOMAIN_CATALOG", "DOMAIN_SCHEMA", "DOMAIN_NAME", "COLUMN_DEFAULT", "IS_NULLABLE",
"DATA_TYPE INT", "PRECISION INT", "SCALE INT", "TYPE_NAME", "SELECTIVITY INT", "CHECK_CONSTRAINT", "REMARKS",
"SQL", "ID INT");
break;
case TRIGGERS:
setObjectName("TRIGGERS");
cols = createColumns("TRIGGER_CATALOG", "TRIGGER_SCHEMA", "TRIGGER_NAME", "TRIGGER_TYPE", "TABLE_CATALOG",
"TABLE_SCHEMA", "TABLE_NAME", "BEFORE BIT", "JAVA_CLASS", "QUEUE_SIZE INT", "NO_WAIT BIT", "REMARKS", "SQL",
"ID INT");
break;
case SESSIONS: {
setObjectName("SESSIONS");
cols = createColumns("ID INT", "USER_NAME", "SESSION_START", "STATEMENT", "STATEMENT_START");
break;
}
case LOCKS: {
setObjectName("LOCKS");
cols = createColumns("TABLE_SCHEMA", "TABLE_NAME", "SESSION_ID INT", "LOCK_TYPE");
break;
}
case SESSION_STATE: {
setObjectName("SESSION_STATE");
cols = createColumns("KEY", "SQL");
break;
}
default:
throw DbException.throwInternalError("type=" + type);
}
setColumns(cols);
if (indexColumnName == null) {
indexColumn = -1;
metaIndex = null;
} else {
indexColumn = getColumn(indexColumnName).getColumnId();
IndexColumn[] indexCols = IndexColumn.wrap(new Column[] { cols[indexColumn] });
metaIndex = new MetaIndex(this, indexCols, false);
}
}
private Column[] createColumns(String... names) {
Column[] cols = new Column[names.length];
for (int i = 0; i < names.length; i++) {
String nameType = names[i];
int idx = nameType.indexOf(' ');
int dataType;
String name;
if (idx < 0) {
dataType = database.getMode().lowerCaseIdentifiers ? Value.STRING_IGNORECASE : Value.STRING;
name = nameType;
} else {
dataType = DataType.getTypeByName(nameType.substring(idx + 1)).type;
name = nameType.substring(0, idx);
}
cols[i] = new Column(name, dataType);
}
return cols;
}
public String getDropSQL() {
return null;
}
public String getCreateSQL() {
return null;
}
public Index addIndex(Session session, String indexName, int indexId, IndexColumn[] cols, IndexType indexType,
boolean create, String indexComment) {
throw DbException.getUnsupportedException("META");
}
public void lock(Session session, boolean exclusive, boolean force) {
// nothing to do
}
public boolean isLockedExclusively() {
return false;
}
private String identifier(String s) {
if (database.getMode().lowerCaseIdentifiers) {
s = s == null ? null : StringUtils.toLowerEnglish(s);
}
return s;
}
private ArrayList<Table> getAllTables(Session session) {
ArrayList<Table> tables = database.getAllTablesAndViews(true);
ArrayList<Table> tempTables = session.getLocalTempTables();
tables.addAll(tempTables);
return tables;
}
private boolean checkIndex(Session session, String value, Value indexFrom, Value indexTo) {
if (value == null || (indexFrom == null && indexTo == null)) {
return true;
}
Database db = session.getDatabase();
if (database.getMode().lowerCaseIdentifiers) {
Value v = ValueStringIgnoreCase.get(value);
if (indexFrom.equals(indexTo) && db.compare(v, indexFrom) != 0) {
return false;
}
} else {
Value v = ValueString.get(value);
if (indexFrom != null && db.compare(v, indexFrom) < 0) {
return false;
}
if (indexTo != null && db.compare(v, indexTo) > 0) {
return false;
}
}
return true;
}
private static String replaceNullWithEmpty(String s) {
return s == null ? "" : s;
}
private boolean hideTable(Table table, Session session) {
return table.isHidden() && session != database.getSystemSession();
}
/**
* Generate the data for the given metadata table using the given first and
* last row filters.
*
* @param session the session
* @param first the first row to return
* @param last the last row to return
* @return the generated rows
*/
public ArrayList<Row> generateRows(Session session, SearchRow first, SearchRow last) {
Value indexFrom = null, indexTo = null;
if (indexColumn >= 0) {
if (first != null) {
indexFrom = first.getValue(indexColumn);
}
if (last != null) {
indexTo = last.getValue(indexColumn);
}
}
ArrayList<Row> rows = New.arrayList();
String catalog = identifier(database.getShortName());
boolean admin = session.getUser().isAdmin();
switch (type) {
case TABLES: {
for (Table table : getAllTables(session)) {
String tableName = identifier(table.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
}
if (hideTable(table, session)) {
continue;
}
String storageType;
if (table.isTemporary()) {
if (table.isGlobalTemporary()) {
storageType = "GLOBAL TEMPORARY";
} else {
storageType = "LOCAL TEMPORARY";
}
} else {
storageType = table.isPersistIndexes() ? "CACHED" : "MEMORY";
}
add(rows,
// TABLE_CATALOG
catalog,
// TABLE_SCHEMA
identifier(table.getSchema().getName()),
// TABLE_NAME
tableName,
// TABLE_TYPE
table.getTableType(),
// STORAGE_TYPE
storageType,
// SQL
table.getCreateSQL(),
// REMARKS
replaceNullWithEmpty(table.getComment()),
// LAST_MODIFICATION
"" + table.getMaxDataModificationId(),
// ID
"" + table.getId(),
// TYPE_NAME
null,
// TABLE_CLASS
table.getClass().getName());
}
break;
}
case COLUMNS: {
for (Table table : getAllTables(session)) {
String tableName = identifier(table.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
}
if (hideTable(table, session)) {
continue;
}
Column[] cols = table.getColumns();
String collation = database.getCompareMode().getName();
for (int j = 0; j < cols.length; j++) {
Column c = cols[j];
Sequence sequence = c.getSequence();
add(rows,
// TABLE_CATALOG
catalog,
// TABLE_SCHEMA
identifier(table.getSchema().getName()),
// TABLE_NAME
tableName,
// COLUMN_NAME
identifier(c.getName()),
// ORDINAL_POSITION
String.valueOf(j + 1),
// COLUMN_DEFAULT
c.getDefaultSQL(),
// IS_NULLABLE
c.isNullable() ? "YES" : "NO",
// DATA_TYPE
"" + DataType.convertTypeToSQLType(c.getType()),
// CHARACTER_MAXIMUM_LENGTH
"" + c.getPrecisionAsInt(),
// CHARACTER_OCTET_LENGTH
"" + c.getPrecisionAsInt(),
// NUMERIC_PRECISION
"" + c.getPrecisionAsInt(),
// NUMERIC_PRECISION_RADIX
"10",
// NUMERIC_SCALE
"" + c.getScale(),
// CHARACTER_SET_NAME
CHARACTER_SET_NAME,
// COLLATION_NAME
collation,
// TYPE_NAME
identifier(DataType.getDataType(c.getType()).name),
// NULLABLE
"" + (c.isNullable() ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls),
// IS_COMPUTED
"" + (c.getComputed() ? "TRUE" : "FALSE"),
// SELECTIVITY
"" + (c.getSelectivity()),
// CHECK_CONSTRAINT
c.getCheckConstraintSQL(session, c.getName()),
// SEQUENCE_NAME
sequence == null ? null : sequence.getName(),
// REMARKS
replaceNullWithEmpty(c.getComment()),
// SOURCE_DATA_TYPE
null);
}
}
break;
}
case INDEXES: {
for (Table table : getAllTables(session)) {
String tableName = identifier(table.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
}
if (hideTable(table, session)) {
continue;
}
ArrayList<Index> indexes = table.getIndexes();
ArrayList<Constraint> constraints = table.getConstraints();
for (int j = 0; indexes != null && j < indexes.size(); j++) {
Index index = indexes.get(j);
if (index.getCreateSQL() == null) {
continue;
}
String constraintName = null;
for (int k = 0; constraints != null && k < constraints.size(); k++) {
Constraint constraint = constraints.get(k);
if (constraint.usesIndex(index)) {
if (index.getIndexType().isPrimaryKey()) {
if (constraint.getConstraintType().equals(Constraint.PRIMARY_KEY)) {
constraintName = constraint.getName();
}
} else {
constraintName = constraint.getName();
}
}
}
IndexColumn[] cols = index.getIndexColumns();
String indexClass;
if (index instanceof MultiVersionIndex) {
indexClass = ((MultiVersionIndex) index).getBaseIndex().getClass().getName();
} else {
indexClass = index.getClass().getName();
}
for (int k = 0; k < cols.length; k++) {
IndexColumn idxCol = cols[k];
Column column = idxCol.column;
add(rows,
// TABLE_CATALOG
catalog,
// TABLE_SCHEMA
identifier(table.getSchema().getName()),
// TABLE_NAME
tableName,
// NON_UNIQUE
index.getIndexType().isUnique() ? "FALSE" : "TRUE",
// INDEX_NAME
identifier(index.getName()),
// ORDINAL_POSITION
"" + (k + 1),
// COLUMN_NAME
identifier(column.getName()),
// CARDINALITY
"0",
// PRIMARY_KEY
index.getIndexType().isPrimaryKey() ? "TRUE" : "FALSE",
// INDEX_TYPE_NAME
index.getIndexType().getSQL(),
// IS_GENERATED
index.getIndexType().getBelongsToConstraint() ? "TRUE" : "FALSE",
// INDEX_TYPE
"" + DatabaseMetaData.tableIndexOther,
// ASC_OR_DESC
(idxCol.sortType & SortOrder.DESCENDING) != 0 ? "D" : "A",
// PAGES
"0",
// FILTER_CONDITION
"",
// REMARKS
replaceNullWithEmpty(index.getComment()),
// SQL
index.getCreateSQL(),
// ID
"" + index.getId(),
// SORT_TYPE
"" + idxCol.sortType,
// CONSTRAINT_NAME
constraintName,
// INDEX_CLASS
indexClass);
}
}
}
break;
}
case TABLE_TYPES: {
add(rows, Table.TABLE);
add(rows, Table.TABLE_LINK);
add(rows, Table.SYSTEM_TABLE);
add(rows, Table.VIEW);
break;
}
case CATALOGS: {
add(rows, catalog);
break;
}
case SETTINGS: {
for (Setting s : database.getAllSettings()) {
String value = s.getStringValue();
if (value == null) {
value = "" + s.getIntValue();
}
add(rows, identifier(s.getName()), value);
}
add(rows, "info.BUILD_ID", "" + Constants.BUILD_ID);
add(rows, "info.VERSION_MAJOR", "" + Constants.VERSION_MAJOR);
add(rows, "info.VERSION_MINOR", "" + Constants.VERSION_MINOR);
add(rows, "info.VERSION", "" + Constants.getFullVersion());
if (admin) {
String[] settings = { "java.runtime.version", "java.vm.name", "java.vendor", "os.name", "os.arch", "os.version",
"sun.os.patch.level", "file.separator", "path.separator", "line.separator", "user.country",
"user.language", "user.variant", "file.encoding" };
for (String s : settings) {
add(rows, "property." + s, Utils.getProperty(s, ""));
}
}
add(rows, "EXCLUSIVE", database.getExclusiveSession() == null ? "FALSE" : "TRUE");
add(rows, "MODE", database.getMode().getName());
add(rows, "MULTI_THREADED", database.isMultiThreaded() ? "1" : "0");
add(rows, "MVCC", database.isMultiVersion() ? "TRUE" : "FALSE");
add(rows, "QUERY_TIMEOUT", "" + session.getQueryTimeout());
add(rows, "LOG", "" + database.getLogMode());
// database settings
ArrayList<String> settingNames = New.arrayList();
HashMap<String, String> s = database.getSettings().getSettings();
for (String k : s.keySet()) {
settingNames.add(k);
}
Collections.sort(settingNames);
for (String k : settingNames) {
add(rows, k, s.get(k));
}
if (database.isPersistent()) {
database.addPersistentMetaInfo(this, rows);
}
break;
}
case TYPE_INFO: {
for (DataType t : DataType.getTypes()) {
if (t.hidden || t.sqlType == Value.NULL) {
continue;
}
add(rows,
// TYPE_NAME
t.name,
// DATA_TYPE
String.valueOf(t.sqlType),
// PRECISION
String.valueOf(MathUtils.convertLongToInt(t.maxPrecision)),
// PREFIX
t.prefix,
// SUFFIX
t.suffix,
// PARAMS
t.params,
// AUTO_INCREMENT
String.valueOf(t.autoIncrement),
// MINIMUM_SCALE
String.valueOf(t.minScale),
// MAXIMUM_SCALE
String.valueOf(t.maxScale),
// RADIX
t.decimal ? "10" : null,
// POS
String.valueOf(t.sqlTypePos),
// CASE_SENSITIVE
String.valueOf(t.caseSensitive),
// NULLABLE
"" + DatabaseMetaData.typeNullable,
// SEARCHABLE
"" + DatabaseMetaData.typeSearchable);
}
break;
}
case HELP: {
String resource = "/org/lealone/res/help.csv";
try {
byte[] data = Utils.getResource(resource);
Reader reader = new InputStreamReader(new ByteArrayInputStream(data));
Csv csv = new Csv();
csv.setLineCommentCharacter('#');
ResultSet rs = csv.read(reader, null);
for (int i = 0; rs.next(); i++) {
add(rows,
// ID
String.valueOf(i),
// SECTION
rs.getString(1).trim(),
// TOPIC
rs.getString(2).trim(),
// SYNTAX
rs.getString(3).trim(),
// TEXT
rs.getString(4).trim());
}
} catch (Exception e) {
throw DbException.convert(e);
}
break;
}
case SEQUENCES: {
for (SchemaObject obj : database.getAllSchemaObjects(DbObject.SEQUENCE)) {
Sequence s = (Sequence) obj;
add(rows,
// SEQUENCE_CATALOG
catalog,
// SEQUENCE_SCHEMA
identifier(s.getSchema().getName()),
// SEQUENCE_NAME
identifier(s.getName()),
// CURRENT_VALUE
String.valueOf(s.getCurrentValue()),
// INCREMENT
String.valueOf(s.getIncrement()),
// IS_GENERATED
s.getBelongsToTable() ? "TRUE" : "FALSE",
// REMARKS
replaceNullWithEmpty(s.getComment()),
// CACHE
String.valueOf(s.getCacheSize()),
// ID
"" + s.getId());
}
break;
}
case USERS: {
for (User u : database.getAllUsers()) {
if (admin || session.getUser() == u) {
add(rows,
// NAME
identifier(u.getName()),
// ADMIN
String.valueOf(u.isAdmin()),
// REMARKS
replaceNullWithEmpty(u.getComment()),
// ID
"" + u.getId());
}
}
break;
}
case ROLES: {
for (Role r : database.getAllRoles()) {
if (admin || session.getUser().isRoleGranted(r)) {
add(rows,
// NAME
identifier(r.getName()),
// REMARKS
replaceNullWithEmpty(r.getComment()),
// ID
"" + r.getId());
}
}
break;
}
case RIGHTS: {
if (admin) {
for (Right r : database.getAllRights()) {
Role role = r.getGrantedRole();
DbObject grantee = r.getGrantee();
String rightType = grantee.getType() == DbObject.USER ? "USER" : "ROLE";
if (role == null) {
Table granted = r.getGrantedTable();
String tableName = identifier(granted.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
}
add(rows,
// GRANTEE
identifier(grantee.getName()),
// GRANTEETYPE
rightType,
// GRANTEDROLE
"",
// RIGHTS
r.getRights(),
// TABLE_SCHEMA
identifier(granted.getSchema().getName()),
// TABLE_NAME
identifier(granted.getName()),
// ID
"" + r.getId());
} else {
add(rows,
// GRANTEE
identifier(grantee.getName()),
// GRANTEETYPE
rightType,
// GRANTEDROLE
identifier(role.getName()),
// RIGHTS
"",
// TABLE_SCHEMA
"",
// TABLE_NAME
"",
// ID
"" + r.getId());
}
}
}
break;
}
case FUNCTION_ALIASES: {
for (SchemaObject aliasAsSchemaObject : database.getAllSchemaObjects(DbObject.FUNCTION_ALIAS)) {
FunctionAlias alias = (FunctionAlias) aliasAsSchemaObject;
for (FunctionAlias.JavaMethod method : alias.getJavaMethods()) {
int returnsResult = method.getDataType() == Value.NULL ? DatabaseMetaData.procedureNoResult
: DatabaseMetaData.procedureReturnsResult;
add(rows,
// ALIAS_CATALOG
catalog,
// ALIAS_SCHEMA
alias.getSchema().getName(),
// ALIAS_NAME
identifier(alias.getName()),
// JAVA_CLASS
alias.getJavaClassName(),
// JAVA_METHOD
alias.getJavaMethodName(),
// DATA_TYPE
"" + DataType.convertTypeToSQLType(method.getDataType()),
// TYPE_NAME
DataType.getDataType(method.getDataType()).name,
// COLUMN_COUNT INT
"" + method.getParameterCount(),
// RETURNS_RESULT SMALLINT
"" + returnsResult,
// REMARKS
replaceNullWithEmpty(alias.getComment()),
// ID
"" + alias.getId(),
// SOURCE
alias.getSource()
// when adding more columns, see also below
);
}
}
for (UserAggregate agg : database.getAllAggregates()) {
int returnsResult = DatabaseMetaData.procedureReturnsResult;
add(rows,
// ALIAS_CATALOG
catalog,
// ALIAS_SCHEMA
Constants.SCHEMA_MAIN,
// ALIAS_NAME
identifier(agg.getName()),
// JAVA_CLASS
agg.getJavaClassName(),
// JAVA_METHOD
"",
// DATA_TYPE
"" + DataType.convertTypeToSQLType(Value.NULL),
// TYPE_NAME
DataType.getDataType(Value.NULL).name,
// COLUMN_COUNT INT
"1",
// RETURNS_RESULT SMALLINT
"" + returnsResult,
// REMARKS
replaceNullWithEmpty(agg.getComment()),
// ID
"" + agg.getId(),
// SOURCE
""
// when adding more columns, see also below
);
}
break;
}
case FUNCTION_COLUMNS: {
for (SchemaObject aliasAsSchemaObject : database.getAllSchemaObjects(DbObject.FUNCTION_ALIAS)) {
FunctionAlias alias = (FunctionAlias) aliasAsSchemaObject;
for (FunctionAlias.JavaMethod method : alias.getJavaMethods()) {
Class<?>[] columnList = method.getColumnClasses();
for (int k = 0; k < columnList.length; k++) {
if (method.hasConnectionParam() && k == 0) {
continue;
}
Class<?> clazz = columnList[k];
int dataType = DataType.getTypeFromClass(clazz);
DataType dt = DataType.getDataType(dataType);
int nullable = clazz.isPrimitive() ? DatabaseMetaData.columnNoNulls : DatabaseMetaData.columnNullable;
add(rows,
// ALIAS_CATALOG
catalog,
// ALIAS_SCHEMA
alias.getSchema().getName(),
// ALIAS_NAME
identifier(alias.getName()),
// JAVA_CLASS
alias.getJavaClassName(),
// JAVA_METHOD
alias.getJavaMethodName(),
// COLUMN_COUNT
"" + method.getParameterCount(),
// POS INT
"" + (k + (method.hasConnectionParam() ? 0 : 1)),
// COLUMN_NAME
"P" + (k + 1),
// DATA_TYPE
"" + DataType.convertTypeToSQLType(dt.type),
// TYPE_NAME
dt.name,
// PRECISION INT
"" + MathUtils.convertLongToInt(dt.defaultPrecision),
// SCALE
"" + dt.defaultScale,
// RADIX
"10",
// NULLABLE SMALLINT
"" + nullable,
// COLUMN_TYPE
"" + DatabaseMetaData.procedureColumnIn,
// REMARKS
"",
// COLUMN_DEFAULT
null);
}
}
}
break;
}
case SCHEMATA: {
String collation = database.getCompareMode().getName();
for (Schema schema : database.getAllSchemas()) {
add(rows,
// CATALOG_NAME
catalog,
// SCHEMA_NAME
identifier(schema.getName()),
// SCHEMA_OWNER
identifier(schema.getOwner().getName()),
// DEFAULT_CHARACTER_SET_NAME
CHARACTER_SET_NAME,
// DEFAULT_COLLATION_NAME
collation,
// IS_DEFAULT
Constants.SCHEMA_MAIN.equals(schema.getName()) ? "TRUE" : "FALSE",
// REMARKS
replaceNullWithEmpty(schema.getComment()),
// ID
"" + schema.getId());
}
break;
}
case TABLE_PRIVILEGES: {
for (Right r : database.getAllRights()) {
Table table = r.getGrantedTable();
if (table == null || hideTable(table, session)) {
continue;
}
String tableName = identifier(table.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
}
addPrivileges(rows, r.getGrantee(), catalog, table, null, r.getRightMask());
}
break;
}
case COLUMN_PRIVILEGES: {
for (Right r : database.getAllRights()) {
Table table = r.getGrantedTable();
if (table == null || hideTable(table, session)) {
continue;
}
String tableName = identifier(table.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
}
DbObject grantee = r.getGrantee();
int mask = r.getRightMask();
for (Column column : table.getColumns()) {
addPrivileges(rows, grantee, catalog, table, column.getName(), mask);
}
}
break;
}
case COLLATIONS: {
for (Locale l : Collator.getAvailableLocales()) {
add(rows,
// NAME
CompareMode.getName(l),
// KEY
l.toString());
}
break;
}
case VIEWS: {
for (Table table : getAllTables(session)) {
if (!table.getTableType().equals(Table.VIEW)) {
continue;
}
String tableName = identifier(table.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
}
TableView view = (TableView) table;
add(rows,
// TABLE_CATALOG
catalog,
// TABLE_SCHEMA
identifier(table.getSchema().getName()),
// TABLE_NAME
tableName,
// VIEW_DEFINITION
table.getCreateSQL(),
// CHECK_OPTION
"NONE",
// IS_UPDATABLE
"NO",
// STATUS
view.isInvalid() ? "INVALID" : "VALID",
// REMARKS
replaceNullWithEmpty(view.getComment()),
// ID
"" + view.getId());
}
break;
}
case IN_DOUBT: {
ArrayList<InDoubtTransaction> prepared = database.getInDoubtTransactions();
if (prepared != null && admin) {
for (InDoubtTransaction prep : prepared) {
add(rows,
// TRANSACTION
prep.getTransaction(),
// STATE
prep.getState());
}
}
break;
}
case CROSS_REFERENCES: {
for (SchemaObject obj : database.getAllSchemaObjects(DbObject.CONSTRAINT)) {
Constraint constraint = (Constraint) obj;
if (!(constraint.getConstraintType().equals(Constraint.REFERENTIAL))) {
continue;
}
ConstraintReferential ref = (ConstraintReferential) constraint;
IndexColumn[] cols = ref.getColumns();
IndexColumn[] refCols = ref.getRefColumns();
Table tab = ref.getTable();
Table refTab = ref.getRefTable();
String tableName = identifier(refTab.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
}
int update = getRefAction(ref.getUpdateAction());
int delete = getRefAction(ref.getDeleteAction());
for (int j = 0; j < cols.length; j++) {
add(rows,
// PKTABLE_CATALOG
catalog,
// PKTABLE_SCHEMA
identifier(refTab.getSchema().getName()),
// PKTABLE_NAME
identifier(refTab.getName()),
// PKCOLUMN_NAME
identifier(refCols[j].column.getName()),
// FKTABLE_CATALOG
catalog,
// FKTABLE_SCHEMA
identifier(tab.getSchema().getName()),
// FKTABLE_NAME
identifier(tab.getName()),
// FKCOLUMN_NAME
identifier(cols[j].column.getName()),
// ORDINAL_POSITION
String.valueOf(j + 1),
// UPDATE_RULE SMALLINT
String.valueOf(update),
// DELETE_RULE SMALLINT
String.valueOf(delete),
// FK_NAME
identifier(ref.getName()),
// PK_NAME
identifier(ref.getUniqueIndex().getName()),
// DEFERRABILITY
"" + DatabaseMetaData.importedKeyNotDeferrable);
}
}
break;
}
case CONSTRAINTS: {
for (SchemaObject obj : database.getAllSchemaObjects(DbObject.CONSTRAINT)) {
Constraint constraint = (Constraint) obj;
String constraintType = constraint.getConstraintType();
String checkExpression = null;
IndexColumn[] indexColumns = null;
Table table = constraint.getTable();
if (hideTable(table, session)) {
continue;
}
Index index = constraint.getUniqueIndex();
String uniqueIndexName = null;
if (index != null) {
uniqueIndexName = index.getName();
}
String tableName = identifier(table.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
}
if (constraintType.equals(Constraint.CHECK)) {
checkExpression = ((ConstraintCheck) constraint).getExpression().getSQL();
} else if (constraintType.equals(Constraint.UNIQUE) || constraintType.equals(Constraint.PRIMARY_KEY)) {
indexColumns = ((ConstraintUnique) constraint).getColumns();
} else if (constraintType.equals(Constraint.REFERENTIAL)) {
indexColumns = ((ConstraintReferential) constraint).getColumns();
}
String columnList = null;
if (indexColumns != null) {
StatementBuilder buff = new StatementBuilder();
for (IndexColumn col : indexColumns) {
buff.appendExceptFirst(",");
buff.append(col.column.getName());
}
columnList = buff.toString();
}
add(rows,
// CONSTRAINT_CATALOG
catalog,
// CONSTRAINT_SCHEMA
identifier(constraint.getSchema().getName()),
// CONSTRAINT_NAME
identifier(constraint.getName()),
// CONSTRAINT_TYPE
constraintType,
// TABLE_CATALOG
catalog,
// TABLE_SCHEMA
identifier(table.getSchema().getName()),
// TABLE_NAME
tableName,
// UNIQUE_INDEX_NAME
uniqueIndexName,
// CHECK_EXPRESSION
checkExpression,
// COLUMN_LIST
columnList,
// REMARKS
replaceNullWithEmpty(constraint.getComment()),
// SQL
constraint.getCreateSQL(),
// ID
"" + constraint.getId());
}
break;
}
case CONSTANTS: {
for (SchemaObject obj : database.getAllSchemaObjects(DbObject.CONSTANT)) {
Constant constant = (Constant) obj;
ValueExpression expr = constant.getValue();
add(rows,
// CONSTANT_CATALOG
catalog,
// CONSTANT_SCHEMA
identifier(constant.getSchema().getName()),
// CONSTANT_NAME
identifier(constant.getName()),
// CONSTANT_TYPE
"" + DataType.convertTypeToSQLType(expr.getType()),
// REMARKS
replaceNullWithEmpty(constant.getComment()),
// SQL
expr.getSQL(),
// ID
"" + constant.getId());
}
break;
}
case DOMAINS: {
for (UserDataType dt : database.getAllUserDataTypes()) {
Column col = dt.getColumn();
add(rows,
// DOMAIN_CATALOG
catalog,
// DOMAIN_SCHEMA
Constants.SCHEMA_MAIN,
// DOMAIN_NAME
identifier(dt.getName()),
// COLUMN_DEFAULT
col.getDefaultSQL(),
// IS_NULLABLE
col.isNullable() ? "YES" : "NO",
// DATA_TYPE
"" + col.getDataType().sqlType,
// PRECISION INT
"" + col.getPrecisionAsInt(),
// SCALE INT
"" + col.getScale(),
// TYPE_NAME
col.getDataType().name,
// SELECTIVITY INT
"" + col.getSelectivity(),
// CHECK_CONSTRAINT
"" + col.getCheckConstraintSQL(session, "VALUE"),
// REMARKS
replaceNullWithEmpty(dt.getComment()),
// SQL
"" + dt.getCreateSQL(),
// ID
"" + dt.getId());
}
break;
}
case TRIGGERS: {
for (SchemaObject obj : database.getAllSchemaObjects(DbObject.TRIGGER)) {
TriggerObject trigger = (TriggerObject) obj;
Table table = trigger.getTable();
add(rows,
// TRIGGER_CATALOG
catalog,
// TRIGGER_SCHEMA
identifier(trigger.getSchema().getName()),
// TRIGGER_NAME
identifier(trigger.getName()),
// TRIGGER_TYPE
trigger.getTypeNameList(),
// TABLE_CATALOG
catalog,
// TABLE_SCHEMA
identifier(table.getSchema().getName()),
// TABLE_NAME
identifier(table.getName()),
// BEFORE BIT
"" + trigger.isBefore(),
// JAVA_CLASS
trigger.getTriggerClassName(),
// QUEUE_SIZE INT
"" + trigger.getQueueSize(),
// NO_WAIT BIT
"" + trigger.isNoWait(),
// REMARKS
replaceNullWithEmpty(trigger.getComment()),
// SQL
trigger.getCreateSQL(),
// ID
"" + trigger.getId());
}
break;
}
case SESSIONS: {
long now = System.currentTimeMillis();
for (Session s : database.getSessions(false)) {
if (admin || s == session) {
Command command = s.getCurrentCommand();
long start = s.getCurrentCommandStart();
if (start == 0) {
start = now;
}
add(rows,
// ID
"" + s.getId(),
// USER_NAME
s.getUser().getName(),
// SESSION_START
new Timestamp(s.getSessionStart()).toString(),
// STATEMENT
command == null ? null : command.toString(),
// STATEMENT_START
new Timestamp(start).toString());
}
}
break;
}
case LOCKS: {
for (Session s : database.getSessions(false)) {
if (admin || s == session) {
for (Table table : s.getLocks()) {
add(rows,
// TABLE_SCHEMA
table.getSchema().getName(),
// TABLE_NAME
table.getName(),
// SESSION_ID
"" + s.getId(),
// LOCK_TYPE
table.isLockedExclusivelyBy(s) ? "WRITE" : "READ");
}
}
}
break;
}
case SESSION_STATE: {
for (String name : session.getVariableNames()) {
Value v = session.getVariable(name);
add(rows,
// KEY
"@" + name,
// SQL
"SET @" + name + " " + v.getSQL());
}
for (Table table : session.getLocalTempTables()) {
add(rows,
// KEY
"TABLE " + table.getName(),
// SQL
table.getCreateSQL());
}
String[] path = session.getSchemaSearchPath();
if (path != null && path.length > 0) {
StatementBuilder buff = new StatementBuilder("SET SCHEMA_SEARCH_PATH ");
for (String p : path) {
buff.appendExceptFirst(", ");
buff.append(StringUtils.quoteIdentifier(p));
}
add(rows,
// KEY
"SCHEMA_SEARCH_PATH",
// SQL
buff.toString());
}
String schema = session.getCurrentSchemaName();
if (schema != null) {
add(rows,
// KEY
"SCHEMA",
// SQL
"SET SCHEMA " + StringUtils.quoteIdentifier(schema));
}
break;
}
default:
DbException.throwInternalError("type=" + type);
}
return rows;
}
private static int getRefAction(int action) {
switch (action) {
case ConstraintReferential.CASCADE:
return DatabaseMetaData.importedKeyCascade;
case ConstraintReferential.RESTRICT:
return DatabaseMetaData.importedKeyRestrict;
case ConstraintReferential.SET_DEFAULT:
return DatabaseMetaData.importedKeySetDefault;
case ConstraintReferential.SET_NULL:
return DatabaseMetaData.importedKeySetNull;
default:
throw DbException.throwInternalError("action=" + action);
}
}
public void removeRow(Session session, Row row) {
throw DbException.getUnsupportedException("META");
}
public void addRow(Session session, Row row) {
throw DbException.getUnsupportedException("META");
}
public void removeChildrenAndResources(Session session) {
throw DbException.getUnsupportedException("META");
}
public void close(Session session) {
// nothing to do
}
public void unlock(Session s) {
// nothing to do
}
private void addPrivileges(ArrayList<Row> rows, DbObject grantee, String catalog, Table table, String column, int rightMask) {
if ((rightMask & Right.SELECT) != 0) {
addPrivilege(rows, grantee, catalog, table, column, "SELECT");
}
if ((rightMask & Right.INSERT) != 0) {
addPrivilege(rows, grantee, catalog, table, column, "INSERT");
}
if ((rightMask & Right.UPDATE) != 0) {
addPrivilege(rows, grantee, catalog, table, column, "UPDATE");
}
if ((rightMask & Right.DELETE) != 0) {
addPrivilege(rows, grantee, catalog, table, column, "DELETE");
}
}
private void addPrivilege(ArrayList<Row> rows, DbObject grantee, String catalog, Table table, String column, String right) {
String isGrantable = "NO";
if (grantee.getType() == DbObject.USER) {
User user = (User) grantee;
if (user.isAdmin()) {
// the right is grantable if the grantee is an admin
isGrantable = "YES";
}
}
if (column == null) {
add(rows,
// GRANTOR
null,
// GRANTEE
identifier(grantee.getName()),
// TABLE_CATALOG
catalog,
// TABLE_SCHEMA
identifier(table.getSchema().getName()),
// TABLE_NAME
identifier(table.getName()),
// PRIVILEGE_TYPE
right,
// IS_GRANTABLE
isGrantable);
} else {
add(rows,
// GRANTOR
null,
// GRANTEE
identifier(grantee.getName()),
// TABLE_CATALOG
catalog,
// TABLE_SCHEMA
identifier(table.getSchema().getName()),
// TABLE_NAME
identifier(table.getName()),
// COLUMN_NAME
identifier(column),
// PRIVILEGE_TYPE
right,
// IS_GRANTABLE
isGrantable);
}
}
public void add(ArrayList<Row> rows, String... strings) {
Value[] values = new Value[strings.length];
for (int i = 0; i < strings.length; i++) {
String s = strings[i];
Value v = (s == null) ? (Value) ValueNull.INSTANCE : ValueString.get(s);
Column col = columns[i];
v = col.convert(v);
values[i] = v;
}
Row row = new Row(values, 1);
row.setKey(rows.size());
rows.add(row);
}
public void checkRename() {
throw DbException.getUnsupportedException("META");
}
public void checkSupportAlter() {
throw DbException.getUnsupportedException("META");
}
public void truncate(Session session) {
throw DbException.getUnsupportedException("META");
}
public long getRowCount(Session session) {
throw DbException.throwInternalError();
}
public boolean canGetRowCount() {
return false;
}
public boolean canDrop() {
return false;
}
public String getTableType() {
return Table.SYSTEM_TABLE;
}
public Index getScanIndex(Session session) {
return new MetaIndex(this, IndexColumn.wrap(columns), true);
}
public ArrayList<Index> getIndexes() {
ArrayList<Index> list = New.arrayList();
if (metaIndex == null) {
return list;
}
list.add(new MetaIndex(this, IndexColumn.wrap(columns), true));
// TODO re-use the index
list.add(metaIndex);
return list;
}
public long getMaxDataModificationId() {
switch (type) {
case SETTINGS:
case IN_DOUBT:
case SESSIONS:
case LOCKS:
case SESSION_STATE:
return Long.MAX_VALUE;
}
return database.getModificationDataId();
}
public Index getUniqueIndex() {
return null;
}
/**
* Get the number of meta table types. Supported meta table
* types are 0 .. this value - 1.
*
* @return the number of meta table types
*/
public static int getMetaTableTypeCount() {
return META_TABLE_TYPE_COUNT;
}
public long getRowCountApproximation() {
return ROW_COUNT_APPROXIMATION;
}
public long getDiskSpaceUsed() {
return 0;
}
public boolean isDeterministic() {
return true;
}
public boolean canReference() {
return false;
}
}