添加SQL统计功能,统计SQL中所使用的表和字段

git-svn-id: http://code.alibabatech.com/svn/druid/trunk@265 b9813039-fb51-4c41-a8b9-e21c2acb5095
This commit is contained in:
wenshao
2011-06-21 14:03:46 +00:00
parent 1c0c56a335
commit d8399a86ee
8 changed files with 812 additions and 2 deletions
@@ -1,15 +1,442 @@
package com.alibaba.druid.sql.dialect.mysql.visitor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
public class MySqlSchemaStatVisitor extends MySqlASTVisitorAdapter {
public boolean visit(SQLPropertyExpr x) {
private HashMap<String, TableStat> tableStats = new HashMap<String, TableStat>();
private Set<Column> fields = new HashSet<Column>();
private final static ThreadLocal<Map<String, String>> aliasLocal = new ThreadLocal<Map<String, String>>();
private final static ThreadLocal<String> currentTableLocal = new ThreadLocal<String>();
private final static ThreadLocal<Mode> modeLocal = new ThreadLocal<Mode>();
public boolean visit(SQLExprTableSource x) {
if (x.getExpr() instanceof SQLIdentifierExpr) {
String ident = ((SQLIdentifierExpr) x.getExpr()).getName();
TableStat stat = tableStats.get(ident);
if (stat == null) {
stat = new TableStat();
tableStats.put(ident, stat);
}
Mode mode = modeLocal.get();
switch (mode) {
case Delete:
stat.deleteCount++;
break;
case Insert:
stat.insertCount++;
break;
case Update:
stat.updateCount++;
break;
case Select:
stat.selectCount++;
break;
default:
break;
}
Map<String, String> aliasMap = aliasLocal.get();
if (aliasMap != null) {
if (x.getAlias() != null) {
aliasMap.put(x.getAlias(), ident);
}
aliasMap.put(ident, ident);
}
}
return false;
}
public boolean visit(SQLSelect x) {
return true;
}
public boolean visit(SQLIdentifierExpr astNode) {
public void endVisit(SQLSelect x) {
}
public boolean visit(SQLUpdateStatement x) {
aliasLocal.set(new HashMap<String, String>());
String ident = x.getTableName().toString();
currentTableLocal.set(ident);
TableStat stat = tableStats.get(ident);
if (stat == null) {
stat = new TableStat();
tableStats.put(ident, stat);
}
stat.updateCount++;
Map<String, String> aliasMap = aliasLocal.get();
aliasMap.put(ident, ident);
accept(x.getItems());
accept(x.getWhere());
return false;
}
public void endVisit(SQLUpdateStatement x) {
aliasLocal.set(null);
}
public boolean visit(MySqlDeleteStatement x) {
aliasLocal.set(new HashMap<String, String>());
x.putAttribute("_original_use_mode", modeLocal.get());
modeLocal.set(Mode.Delete);
aliasLocal.set(new HashMap<String, String>());
if (x.getTableNames().size() == 1) {
String ident = ((SQLIdentifierExpr) x.getTableNames().get(0)).getName();
currentTableLocal.set(ident);
}
for (SQLName tableName : x.getTableNames()) {
String ident = tableName.toString();
TableStat stat = tableStats.get(ident);
if (stat == null) {
stat = new TableStat();
tableStats.put(ident, stat);
}
stat.deleteCount++;
}
accept(x.getWhere());
accept(x.getFrom());
accept(x.getUsing());
accept(x.getOrderBy());
accept(x.getLimit());
return false;
}
public void endVisit(MySqlDeleteStatement x) {
aliasLocal.set(null);
}
public boolean visit(SQLSelectStatement x) {
aliasLocal.set(new HashMap<String, String>());
return true;
}
public void endVisit(SQLSelectStatement x) {
aliasLocal.set(null);
}
@Override
public void endVisit(MySqlInsertStatement x) {
Mode originalMode = (Mode) x.getAttribute("_original_use_mode");
modeLocal.set(originalMode);
}
@Override
public boolean visit(MySqlInsertStatement x) {
x.putAttribute("_original_use_mode", modeLocal.get());
modeLocal.set(Mode.Insert);
aliasLocal.set(new HashMap<String, String>());
String originalTable = currentTableLocal.get();
if (x.getTableName() instanceof SQLIdentifierExpr) {
String ident = ((SQLIdentifierExpr) x.getTableName()).getName();
currentTableLocal.set(ident);
x.putAttribute("_old_local_", originalTable);
TableStat stat = tableStats.get(ident);
if (stat == null) {
stat = new TableStat();
tableStats.put(ident, stat);
}
stat.insertCount++;
Map<String, String> aliasMap = aliasLocal.get();
if (aliasMap != null) {
if (x.getAlias() != null) {
aliasMap.put(x.getAlias(), ident);
}
aliasMap.put(ident, ident);
}
}
accept(x.getColumns());
accept(x.getValuesList());
accept(x.getQuery());
accept(x.getDuplicateKeyUpdate());
return false;
}
@Override
public boolean visit(SQLInsertStatement x) {
x.putAttribute("_original_use_mode", modeLocal.get());
modeLocal.set(Mode.Insert);
aliasLocal.set(new HashMap<String, String>());
String originalTable = currentTableLocal.get();
if (x.getTableName() instanceof SQLIdentifierExpr) {
String ident = ((SQLIdentifierExpr) x.getTableName()).getName();
currentTableLocal.set(ident);
x.putAttribute("_old_local_", originalTable);
TableStat stat = tableStats.get(ident);
if (stat == null) {
stat = new TableStat();
tableStats.put(ident, stat);
}
stat.insertCount++;
Map<String, String> aliasMap = aliasLocal.get();
if (aliasMap != null) {
if (x.getAlias() != null) {
aliasMap.put(x.getAlias(), ident);
}
aliasMap.put(ident, ident);
}
}
accept(x.getColumns());
accept(x.getQuery());
return false;
}
protected void accept(SQLObject x) {
if (x != null) {
x.accept(this);
}
}
protected void accept(List<? extends SQLObject> nodes) {
for (int i = 0, size = nodes.size(); i < size; ++i) {
accept(nodes.get(i));
}
}
public boolean visit(SQLSelectQueryBlock x) {
x.putAttribute("_original_use_mode", modeLocal.get());
modeLocal.set(Mode.Select);
String originalTable = currentTableLocal.get();
if (x.getFrom() instanceof SQLExprTableSource) {
SQLExprTableSource tableSource = (SQLExprTableSource) x.getFrom();
if (tableSource.getExpr() instanceof SQLIdentifierExpr) {
String ident = ((SQLIdentifierExpr) tableSource.getExpr()).getName();
currentTableLocal.set(ident);
x.putAttribute("_old_local_", originalTable);
}
}
x.getFrom().accept(this); // 提前执行,获得aliasMap
return true;
}
public void endVisit(SQLSelectQueryBlock x) {
String originalTable = (String) x.getAttribute("_old_local_");
currentTableLocal.set(originalTable);
Mode originalMode = (Mode) x.getAttribute("_original_use_mode");
modeLocal.set(originalMode);
}
public boolean visit(SQLJoinTableSource x) {
return true;
}
public boolean visit(SQLPropertyExpr x) {
if (x.getOwner() instanceof SQLIdentifierExpr) {
String owner = ((SQLIdentifierExpr) x.getOwner()).getName();
Map<String, String> aliasMap = aliasLocal.get();
if (aliasMap != null) {
String table = aliasMap.get(owner);
fields.add(new Column(table, x.getName()));
}
}
return false;
}
public boolean visit(SQLIdentifierExpr x) {
String currentTable = currentTableLocal.get();
if (currentTable != null) {
fields.add(new Column(currentTable, x.getName()));
}
return false;
}
public Map<String, TableStat> getTables() {
return tableStats;
}
public Set<Column> getFields() {
return fields;
}
public static class Column {
private String table;
private String name;
public Column(){
}
public Column(String table, String name){
this.table = table;
this.name = name;
}
public String getTable() {
return table;
}
public void setTable(String table) {
this.table = table;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int hashCode() {
int tableHashCode = table != null ? table.hashCode() : 0;
int nameHashCode = name != null ? name.hashCode() : 0;
return tableHashCode + nameHashCode;
}
public String toString() {
if (table != null) {
return table + "." + name;
}
return name;
}
public boolean equals(Object obj) {
Column column = (Column) obj;
if (table == null) {
if (column.getTable() != null) {
return false;
}
} else {
if (!table.equals(column.getTable())) {
return false;
}
}
if (name == null) {
if (column.getName() != null) {
return false;
}
} else {
if (!name.equals(column.getName())) {
return false;
}
}
return true;
}
}
public static enum Mode {
Insert(1), Update(2), Delete(4), Select(8);
public final int mark;
private Mode(int mark){
this.mark = mark;
}
}
public static class TableStat {
private int selectCount = 0;
private int updateCount = 0;
private int deleteCount = 0;
private int insertCount = 0;
public int getSelectCount() {
return selectCount;
}
public void setSelectCount(int selectCount) {
this.selectCount = selectCount;
}
public int getUpdateCount() {
return updateCount;
}
public void setUpdateCount(int updateCount) {
this.updateCount = updateCount;
}
public int getDeleteCount() {
return deleteCount;
}
public void setDeleteCount(int deleteCount) {
this.deleteCount = deleteCount;
}
public int getInsertCount() {
return insertCount;
}
public void setInsertCount(int insertCount) {
this.insertCount = insertCount;
}
public String toString() {
StringBuilder buf = new StringBuilder(4);
if (insertCount > 0) {
buf.append("C");
}
if (updateCount > 0) {
buf.append("U");
}
if (selectCount > 0) {
buf.append("R");
}
if (deleteCount > 0) {
buf.append("D");
}
return buf.toString();
}
}
}
@@ -0,0 +1,87 @@
package com.alibaba.druid.sql;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;
import junit.framework.Assert;
import junit.framework.TestCase;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
public class OnlineSQLTest2 extends TestCase {
private String url = "jdbc:mysql://10.20.36.26/dragoon_v25_monitordb_test";
private String user = "dragoon";
private String password = "dragoon";
public void test_list_sql() throws Exception {
Connection conn = DriverManager.getConnection(url, user, password);
int count = 0;
String sql = "SELECT id, value FROM m_sql_const LIMIT 100";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt(1);
String value = rs.getString(2);
if (value.indexOf('(') != -1) {
continue;
}
boolean sqlFlag = false;
String lowerSql = value.toLowerCase();
if (lowerSql.startsWith("insert") || lowerSql.startsWith("select") || lowerSql.startsWith("upate")
|| lowerSql.startsWith("delete") || lowerSql.startsWith("create") || lowerSql.startsWith("drop")) {
sqlFlag = true;
}
if (!sqlFlag) {
continue;
}
System.out.println(value);
mysqlStat(id, lowerSql);
System.out.println();
count++;
}
rs.close();
stmt.close();
System.out.println("COUNT : " + count);
conn.close();
}
void mysqlStat(int id, String sql) throws Exception {
sql = sql.trim();
boolean sqlFlag = false;
String lowerSql = sql.toLowerCase();
if (lowerSql.startsWith("insert") || lowerSql.startsWith("select") || lowerSql.startsWith("upate") || lowerSql.startsWith("delete")
|| lowerSql.startsWith("create") || lowerSql.startsWith("drop")) {
sqlFlag = true;
}
if (!sqlFlag) {
return;
}
MySqlStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLStatement statemen = statementList.get(0);
Assert.assertEquals(1, statementList.size());
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statemen.accept(visitor);
System.out.println("Tables : " + visitor.getTables());
System.out.println("fields : " + visitor.getFields());
}
}
@@ -0,0 +1,42 @@
package com.alibaba.druid.sql.mysql.bvt.visitor;
import java.util.List;
import junit.framework.Assert;
import junit.framework.TestCase;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor.Column;
public class MySqlSchemaStatVisitorTest1 extends TestCase {
public void test_0() throws Exception {
String sql = "select a.name, b.name FROM users a, usergroups b on a.groupId = b.id";
MySqlStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLStatement statemen = statementList.get(0);
Assert.assertEquals(1, statementList.size());
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statemen.accept(visitor);
System.out.println(sql);
System.out.println("Tables : " + visitor.getTables());
System.out.println("fields : " + visitor.getFields());
Assert.assertEquals(2, visitor.getTables().size());
Assert.assertEquals(true, visitor.getTables().containsKey("users"));
Assert.assertEquals(true, visitor.getTables().containsKey("usergroups"));
Assert.assertEquals(4, visitor.getFields().size());
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "groupId")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "name")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("usergroups", "id")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("usergroups", "name")));
}
}
@@ -0,0 +1,89 @@
package com.alibaba.druid.sql.mysql.bvt.visitor;
import java.util.List;
import junit.framework.Assert;
import junit.framework.TestCase;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor.Column;
public class MySqlSchemaStatVisitorTest2 extends TestCase {
public void test_0() throws Exception {
String sql = "select id, name FROM users";
MySqlStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLStatement statemen = statementList.get(0);
Assert.assertEquals(1, statementList.size());
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statemen.accept(visitor);
System.out.println(sql);
System.out.println("Tables : " + visitor.getTables());
System.out.println("fields : " + visitor.getFields());
Assert.assertEquals(1, visitor.getTables().size());
Assert.assertEquals(true, visitor.getTables().containsKey("users"));
Assert.assertEquals(2, visitor.getFields().size());
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "id")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "name")));
}
public void test_2() throws Exception {
String sql = "select id, name FROM users a";
MySqlStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLStatement statemen = statementList.get(0);
Assert.assertEquals(1, statementList.size());
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statemen.accept(visitor);
System.out.println(sql);
System.out.println("Tables : " + visitor.getTables());
System.out.println("fields : " + visitor.getFields());
Assert.assertEquals(1, visitor.getTables().size());
Assert.assertEquals(true, visitor.getTables().containsKey("users"));
Assert.assertEquals(2, visitor.getFields().size());
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "id")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "name")));
}
public void test_3() throws Exception {
String sql = "select id, a.name FROM users a";
MySqlStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLStatement statemen = statementList.get(0);
Assert.assertEquals(1, statementList.size());
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statemen.accept(visitor);
System.out.println(sql);
System.out.println("Tables : " + visitor.getTables());
System.out.println("fields : " + visitor.getFields());
Assert.assertEquals(1, visitor.getTables().size());
Assert.assertEquals(true, visitor.getTables().containsKey("users"));
Assert.assertEquals(2, visitor.getFields().size());
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "id")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "name")));
}
}
@@ -0,0 +1,45 @@
package com.alibaba.druid.sql.mysql.bvt.visitor;
import java.util.List;
import junit.framework.Assert;
import junit.framework.TestCase;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor.Column;
public class MySqlSchemaStatVisitorTest3 extends TestCase {
public void test_0() throws Exception {
String sql = "insert into users2 (id2, name2) select id, name FROM users where loginCount > 1";
MySqlStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLStatement statemen = statementList.get(0);
Assert.assertEquals(1, statementList.size());
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statemen.accept(visitor);
System.out.println(sql);
System.out.println("Tables : " + visitor.getTables());
System.out.println("fields : " + visitor.getFields());
Assert.assertEquals(2, visitor.getTables().size());
Assert.assertEquals(true, visitor.getTables().containsKey("users"));
Assert.assertEquals(true, visitor.getTables().containsKey("users2"));
Assert.assertEquals(5, visitor.getFields().size());
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "id")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "name")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "loginCount")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users2", "name2")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users2", "id2")));
}
}
@@ -0,0 +1,40 @@
package com.alibaba.druid.sql.mysql.bvt.visitor;
import java.util.List;
import junit.framework.Assert;
import junit.framework.TestCase;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor.Column;
public class MySqlSchemaStatVisitorTest4 extends TestCase {
public void test_0() throws Exception {
String sql = "insert into users (id, name) values(?, ?)";
MySqlStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLStatement statemen = statementList.get(0);
Assert.assertEquals(1, statementList.size());
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statemen.accept(visitor);
System.out.println(sql);
System.out.println("Tables : " + visitor.getTables());
System.out.println("fields : " + visitor.getFields());
Assert.assertEquals(1, visitor.getTables().size());
Assert.assertEquals(true, visitor.getTables().containsKey("users"));
Assert.assertEquals(2, visitor.getFields().size());
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "id")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "name")));
}
}
@@ -0,0 +1,40 @@
package com.alibaba.druid.sql.mysql.bvt.visitor;
import java.util.List;
import junit.framework.Assert;
import junit.framework.TestCase;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor.Column;
public class MySqlSchemaStatVisitorTest_Delete extends TestCase {
public void test_0() throws Exception {
String sql = "delete from users where id = ? or name = ?";
MySqlStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLStatement statemen = statementList.get(0);
Assert.assertEquals(1, statementList.size());
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statemen.accept(visitor);
System.out.println(sql);
System.out.println("Tables : " + visitor.getTables());
System.out.println("fields : " + visitor.getFields());
Assert.assertEquals(1, visitor.getTables().size());
Assert.assertEquals(true, visitor.getTables().containsKey("users"));
Assert.assertEquals(2, visitor.getFields().size());
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "id")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "name")));
}
}
@@ -0,0 +1,40 @@
package com.alibaba.druid.sql.mysql.bvt.visitor;
import java.util.List;
import junit.framework.Assert;
import junit.framework.TestCase;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor.Column;
public class MySqlSchemaStatVisitorTest_Update extends TestCase {
public void test_0() throws Exception {
String sql = "update users set loginCount = loginCount + 1 where id = ?";
MySqlStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> statementList = parser.parseStatementList();
SQLStatement statemen = statementList.get(0);
Assert.assertEquals(1, statementList.size());
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statemen.accept(visitor);
System.out.println(sql);
System.out.println("Tables : " + visitor.getTables());
System.out.println("fields : " + visitor.getFields());
Assert.assertEquals(1, visitor.getTables().size());
Assert.assertEquals(true, visitor.getTables().containsKey("users"));
Assert.assertEquals(2, visitor.getFields().size());
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "id")));
Assert.assertEquals(true, visitor.getFields().contains(new Column("users", "loginCount")));
}
}