Sql言語解析器の実現例
最近プロジェクトはもうすぐreleaseになります。手元も忙しくないです。Sql言語解析器の枠組みを3時間かけて書きました。製品のカスタム言語の実現方法にも使えます。本文はinsertのcommandを実現しました。他のコマンドは参照して実現できます。
MySQL.java:
Mysql Shell.java:package com.cisco.gendwang; public class MySQL { public static void main(String[] args) { MysqlShell shell = MysqlShell.getInstance(); shell.run(); } }
Sql Paser.java:package com.cisco.gendwang; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class MysqlShell { private static final MysqlShell instance = new MysqlShell(); private MysqlShell() { } public static MysqlShell getInstance() { return instance; } public void run() { Context context = new Context(); SqlParser parser = new SqlParser(context); BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); while(true) { System.out.print("mysql> "); //clear the context and the parser context.clear(); parser.reset(); //read the command from the standard input String commandString = null; try { commandString = input.readLine(); if(commandString.equalsIgnoreCase("exit")) { break; } } catch (IOException e) { e.printStackTrace(); break; } //parse and execute the command parser.setCommandString(commandString); try { Command command = parser.parse(); command.execute(context); } catch (SqlParserException e) { System.out.println(e.getMessage()); } catch (SqlExecutionException e) { System.out.println(e.getMessage()); } } } }
Conttext.java:package com.cisco.gendwang; public class SqlParser { private static final String SQL_SYNTAX_ERROR = "ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1"; private final Context context; private String commandString; public SqlParser(Context context) { this.context = context; } public void reset() { commandString = null; } public void setCommandString(String commandString) { this.commandString = commandString; } public Command parse() throws SqlParserException { if(commandString.startsWith("insert")) { return parseInsertCommand(); } return null; } private Command parseInsertCommand() throws SqlParserException { if(commandString.length() <= "insert".length()) { throw new SqlParserException(SQL_SYNTAX_ERROR); } //remove the insert String left = commandString.substring("insert".length()); left = left.trim(); if(left.length() <= "into".length()) { throw new SqlParserException(SQL_SYNTAX_ERROR); } //remove the into left = left.substring("into".length()); left = left.trim(); String tableName = context.getTableName(); if(left.length() <= tableName.length()) { throw new SqlParserException(SQL_SYNTAX_ERROR); } //remove the table name left = left.substring(tableName.length()); left = left.trim(); if(left.length() <= "values".length()) { throw new SqlParserException(SQL_SYNTAX_ERROR); } //remove the values left = left.substring("values".length()); left = left.trim(); if(left.length() <= "(".length()) { throw new SqlParserException(SQL_SYNTAX_ERROR); } //remove the ( left = left.substring("(".length()); left = left.trim(); if(!(left.endsWith(");"))) { throw new SqlParserException(SQL_SYNTAX_ERROR); } //remove the ); left = left.substring(0, left.length() - 2); String[] vals = left.split("\\s*,\\s*"); //build the InsertCommand InsertCommand insert = new InsertCommand(); for(String val: vals) { insert.addParam(new Constant(val)); } return insert; } }
Row.java:package com.cisco.gendwang; import java.util.LinkedList; import java.util.List; public class Context { private final String DATABASE_NAME = "HRSystem"; private final String TABLE_NAME = "employee"; private final List<Row> rows = new LinkedList<Row>(); public Context() { } public void clear() { } public String getDatabaseName() { return DATABASE_NAME; } public String getTableName() { return TABLE_NAME; } public List<Row> getRows() { return rows; } }
Command.java:package com.cisco.gendwang; public class Row { private int id; private String name; private int salary; private String email; private Row(RowBuilder builder) { id = builder.id; name = builder.name; salary = builder.salary; email = builder.email; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public boolean equals(Object obj) { if(!(obj instanceof Row)) { return false; } Row row = (Row)obj; return (id == row.id); } @Override public int hashCode() { int result = 17; result = 31 * result + id; return result; } public static class RowBuilder { private int id; private String name; private int salary; private String email; public RowBuilder() { } public RowBuilder setId(int id) { this.id = id; return this; } public RowBuilder setName(String name) { this.name = name; return this; } public RowBuilder setSalary(int salary) { this.salary = salary; return this; } public RowBuilder setEmail(String email) { this.email = email; return this; } public Row build() { //validation can be handled here return new Row(this); } } }
Contstant.java:package com.cisco.gendwang; public abstract class Command { public abstract Object execute(Context ctx) throws SqlExecutionException; }
InsertCommand.java:package com.cisco.gendwang; public class Constant extends Command { private Object value = null; public Constant(String value) { char ch = value.charAt(0); if((ch == '"') || (ch == '\'') ) { this.value = value.substring(1, value.length() - 1); } else if(Character.isDigit(ch)) { this.value = Integer.parseInt(value); } } @Override public Object execute(Context ctx) throws SqlExecutionException { return value; } }
Sql PaserException.java:package com.cisco.gendwang; import java.util.LinkedList; import java.util.List; public class InsertCommand extends Command { private List<Constant> params = new LinkedList<Constant>(); public InsertCommand() { } public void addParam(Constant param) { params.add(param); } public Object execute(Context ctx) throws SqlExecutionException { //validation could be put here //insert into the table int id = (int)params.get(0).execute(ctx); String name = (String)params.get(1).execute(ctx); int salary = (int)params.get(2).execute(ctx); String email = (String)params.get(3).execute(ctx); Row.RowBuilder builder = new Row.RowBuilder(); builder.setId(id); builder.setName(name); builder.setSalary(salary); builder.setEmail(email); Row row = builder.build(); List<Row> rows = ctx.getRows(); rows.add(row); return null; } }
Sql Execution Exception.java:package com.cisco.gendwang; public class SqlParserException extends Exception { public SqlParserException(String msg) { super(msg); } }
package com.cisco.gendwang; public class SqlExecutionException extends Exception { public SqlExecutionException(String msg) { super(msg); } }