package com.cube.sql;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* SQL Parser for Cube database
*
* Supports standard SQL syntax:
* - SELECT * FROM table WHERE id = 'value'
* - INSERT INTO table (col1, col2) VALUES ('val1', 'val2')
* - UPDATE table SET col1='val1' WHERE id='value'
* - DELETE FROM table WHERE id='value'
* - CREATE TABLE table (col1 TYPE, col2 TYPE, ...)
* - DROP TABLE table
*/
public class SQLParser {
public enum SQLType {
SELECT,
INSERT,
UPDATE,
DELETE,
CREATE_TABLE,
DROP_TABLE,
DESCRIBE,
SHOW_TABLES,
UNKNOWN
}
public static class ParsedSQL {
private final SQLType type;
private final String table;
private final String keyspace;
private final Map<String, String> columns;
private final Map<String, String> whereClause;
private final List<String> selectColumns;
private final Map<String, String> columnDefinitions;
private final String primaryKey;
public ParsedSQL(SQLType type, String keyspace, String table) {
this.type = type;
this.keyspace = keyspace;
this.table = table;
this.columns = new LinkedHashMap<>();
this.whereClause = new LinkedHashMap<>();
this.selectColumns = new ArrayList<>();
this.columnDefinitions = new LinkedHashMap<>();
this.primaryKey = null;
}
public ParsedSQL(SQLType type, String keyspace, String table,
Map<String, String> columns, Map<String, String> whereClause,
List<String> selectColumns, Map<String, String> columnDefinitions,
String primaryKey) {
this.type = type;
this.keyspace = keyspace;
this.table = table;
this.columns = columns != null ? columns : new LinkedHashMap<>();
this.whereClause = whereClause != null ? whereClause : new LinkedHashMap<>();
this.selectColumns = selectColumns != null ? selectColumns : new ArrayList<>();
this.columnDefinitions = columnDefinitions != null ? columnDefinitions : new LinkedHashMap<>();
this.primaryKey = primaryKey;
}
public SQLType getType() { return type; }
public String getTable() { return table; }
public String getKeyspace() { return keyspace; }
public Map<String, String> getColumns() { return columns; }
public Map<String, String> getWhereClause() { return whereClause; }
public List<String> getSelectColumns() { return selectColumns; }
public Map<String, String> getColumnDefinitions() { return columnDefinitions; }
public String getPrimaryKey() { return primaryKey; }
@Override
public String toString() {
return "ParsedSQL{type=" + type + ", table=" + keyspace + "." + table + "}";
}
}
/**
* Parse SQL statement
*/
public static ParsedSQL parse(String sql) throws IllegalArgumentException {
if (sql == null || sql.trim().isEmpty()) {
throw new IllegalArgumentException("SQL statement is empty");
}
sql = sql.trim();
if (sql.endsWith(";")) {
sql = sql.substring(0, sql.length() - 1).trim();
}
String upperSQL = sql.toUpperCase();
if (upperSQL.startsWith("SELECT")) {
return parseSelect(sql);
} else if (upperSQL.startsWith("INSERT")) {
return parseInsert(sql);
} else if (upperSQL.startsWith("UPDATE")) {
return parseUpdate(sql);
} else if (upperSQL.startsWith("DELETE")) {
return parseDelete(sql);
} else if (upperSQL.startsWith("CREATE TABLE")) {
return parseCreateTable(sql);
} else if (upperSQL.startsWith("DROP TABLE")) {
return parseDropTable(sql);
} else if (upperSQL.startsWith("DESCRIBE") || upperSQL.startsWith("DESC")) {
return parseDescribe(sql);
} else if (upperSQL.startsWith("SHOW TABLES")) {
return parseShowTables(sql);
} else {
throw new IllegalArgumentException("Unsupported SQL statement: " + sql);
}
}
/**
* Parse SELECT statement
* Examples:
* - SELECT * FROM users.profiles WHERE id = 'user-1'
* - SELECT name, email FROM users.profiles WHERE id = 'user-1'
* - SELECT * FROM profiles WHERE id = 'user-1' (default keyspace)
*/
private static ParsedSQL parseSelect(String sql) {
// Pattern: SELECT columns FROM [keyspace.]table WHERE conditions
Pattern pattern = Pattern.compile(
"SELECT\\s+(.+?)\\s+FROM\\s+([\\w.]+)(?:\\s+WHERE\\s+(.+))?",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = pattern.matcher(sql);
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid SELECT syntax: " + sql);
}
String columnsStr = matcher.group(1).trim();
String tableRef = matcher.group(2).trim();
String whereStr = matcher.group(3);
// Parse table reference
String[] parts = tableRef.split("\\.");
String keyspace = parts.length > 1 ? parts[0] : "default";
String table = parts.length > 1 ? parts[1] : parts[0];
// Parse columns
List<String> selectColumns = new ArrayList<>();
if ("*".equals(columnsStr)) {
selectColumns.add("*");
} else {
for (String col : columnsStr.split(",")) {
selectColumns.add(col.trim());
}
}
// Parse WHERE clause
Map<String, String> whereClause = parseWhereClause(whereStr);
return new ParsedSQL(SQLType.SELECT, keyspace, table, null, whereClause,
selectColumns, null, null);
}
/**
* Parse INSERT statement
* Example: INSERT INTO users.profiles (id, name, email) VALUES ('user-1', 'Alice', 'alice@example.com')
*/
private static ParsedSQL parseInsert(String sql) {
// Pattern: INSERT INTO [keyspace.]table (columns) VALUES (values)
Pattern pattern = Pattern.compile(
"INSERT\\s+INTO\\s+([\\w.]+)\\s*\\(([^)]+)\\)\\s*VALUES\\s*\\(([^)]+)\\)",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = pattern.matcher(sql);
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid INSERT syntax: " + sql);
}
String tableRef = matcher.group(1).trim();
String columnsStr = matcher.group(2).trim();
String valuesStr = matcher.group(3).trim();
// Parse table reference
String[] parts = tableRef.split("\\.");
String keyspace = parts.length > 1 ? parts[0] : "default";
String table = parts.length > 1 ? parts[1] : parts[0];
// Parse columns and values
String[] columns = columnsStr.split(",");
String[] values = parseValues(valuesStr);
if (columns.length != values.length) {
throw new IllegalArgumentException("Column count doesn't match value count");
}
Map<String, String> columnValues = new LinkedHashMap<>();
for (int i = 0; i < columns.length; i++) {
columnValues.put(columns[i].trim(), values[i]);
}
return new ParsedSQL(SQLType.INSERT, keyspace, table, columnValues, null,
null, null, null);
}
/**
* Parse UPDATE statement
* Example: UPDATE users.profiles SET name='Alice Johnson', age='31' WHERE id='user-1'
*/
private static ParsedSQL parseUpdate(String sql) {
// Pattern: UPDATE [keyspace.]table SET assignments WHERE conditions
Pattern pattern = Pattern.compile(
"UPDATE\\s+([\\w.]+)\\s+SET\\s+(.+?)\\s+WHERE\\s+(.+)",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = pattern.matcher(sql);
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid UPDATE syntax: " + sql);
}
String tableRef = matcher.group(1).trim();
String setStr = matcher.group(2).trim();
String whereStr = matcher.group(3).trim();
// Parse table reference
String[] parts = tableRef.split("\\.");
String keyspace = parts.length > 1 ? parts[0] : "default";
String table = parts.length > 1 ? parts[1] : parts[0];
// Parse SET clause
Map<String, String> columns = parseSetClause(setStr);
// Parse WHERE clause
Map<String, String> whereClause = parseWhereClause(whereStr);
return new ParsedSQL(SQLType.UPDATE, keyspace, table, columns, whereClause,
null, null, null);
}
/**
* Parse DELETE statement
* Example: DELETE FROM users.profiles WHERE id='user-1'
*/
private static ParsedSQL parseDelete(String sql) {
// Pattern: DELETE FROM [keyspace.]table WHERE conditions
Pattern pattern = Pattern.compile(
"DELETE\\s+FROM\\s+([\\w.]+)\\s+WHERE\\s+(.+)",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = pattern.matcher(sql);
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid DELETE syntax: " + sql);
}
String tableRef = matcher.group(1).trim();
String whereStr = matcher.group(2).trim();
// Parse table reference
String[] parts = tableRef.split("\\.");
String keyspace = parts.length > 1 ? parts[0] : "default";
String table = parts.length > 1 ? parts[1] : parts[0];
// Parse WHERE clause
Map<String, String> whereClause = parseWhereClause(whereStr);
return new ParsedSQL(SQLType.DELETE, keyspace, table, null, whereClause,
null, null, null);
}
/**
* Parse CREATE TABLE statement
* Example: CREATE TABLE users.profiles (id TEXT PRIMARY KEY, name TEXT, email TEXT)
*/
private static ParsedSQL parseCreateTable(String sql) {
// Pattern: CREATE TABLE [keyspace.]table (column_definitions)
Pattern pattern = Pattern.compile(
"CREATE\\s+TABLE\\s+([\\w.]+)\\s*\\(([^)]+)\\)",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = pattern.matcher(sql);
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid CREATE TABLE syntax: " + sql);
}
String tableRef = matcher.group(1).trim();
String columnsStr = matcher.group(2).trim();
// Parse table reference
String[] parts = tableRef.split("\\.");
String keyspace = parts.length > 1 ? parts[0] : "default";
String table = parts.length > 1 ? parts[1] : parts[0];
// Parse column definitions
Map<String, String> columnDefs = new LinkedHashMap<>();
String primaryKey = null;
String[] columns = columnsStr.split(",");
for (String col : columns) {
col = col.trim();
// Check for PRIMARY KEY constraint
if (col.toUpperCase().contains("PRIMARY KEY")) {
String[] colParts = col.split("\\s+");
String colName = colParts[0];
String colType = colParts[1];
columnDefs.put(colName, colType);
primaryKey = colName;
} else {
String[] colParts = col.split("\\s+", 2);
if (colParts.length == 2) {
columnDefs.put(colParts[0], colParts[1]);
}
}
}
// If no PRIMARY KEY specified, first column is primary key
if (primaryKey == null && !columnDefs.isEmpty()) {
primaryKey = columnDefs.keySet().iterator().next();
}
return new ParsedSQL(SQLType.CREATE_TABLE, keyspace, table, null, null,
null, columnDefs, primaryKey);
}
/**
* Parse DROP TABLE statement
* Example: DROP TABLE users.profiles
*/
private static ParsedSQL parseDropTable(String sql) {
Pattern pattern = Pattern.compile(
"DROP\\s+TABLE\\s+([\\w.]+)",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = pattern.matcher(sql);
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid DROP TABLE syntax: " + sql);
}
String tableRef = matcher.group(1).trim();
String[] parts = tableRef.split("\\.");
String keyspace = parts.length > 1 ? parts[0] : "default";
String table = parts.length > 1 ? parts[1] : parts[0];
return new ParsedSQL(SQLType.DROP_TABLE, keyspace, table, null, null,
null, null, null);
}
/**
* Parse DESCRIBE statement
* Example: DESCRIBE users.profiles
*/
private static ParsedSQL parseDescribe(String sql) {
Pattern pattern = Pattern.compile(
"(?:DESCRIBE|DESC)\\s+([\\w.]+)",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = pattern.matcher(sql);
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid DESCRIBE syntax: " + sql);
}
String tableRef = matcher.group(1).trim();
String[] parts = tableRef.split("\\.");
String keyspace = parts.length > 1 ? parts[0] : "default";
String table = parts.length > 1 ? parts[1] : parts[0];
return new ParsedSQL(SQLType.DESCRIBE, keyspace, table, null, null,
null, null, null);
}
/**
* Parse SHOW TABLES statement
*/
private static ParsedSQL parseShowTables(String sql) {
return new ParsedSQL(SQLType.SHOW_TABLES, "default", null, null, null,
null, null, null);
}
/**
* Parse WHERE clause
* Example: id='user-1' AND status='active'
*/
private static Map<String, String> parseWhereClause(String whereStr) {
Map<String, String> where = new LinkedHashMap<>();
if (whereStr == null || whereStr.trim().isEmpty()) {
return where;
}
// Split by AND (simple implementation)
String[] conditions = whereStr.split("\\s+AND\\s+", -1);
for (String condition : conditions) {
condition = condition.trim();
// Parse: column = 'value' or column='value'
Pattern pattern = Pattern.compile("([\\w]+)\\s*=\\s*['\"]?([^'\"]+)['\"]?");
Matcher matcher = pattern.matcher(condition);
if (matcher.find()) {
where.put(matcher.group(1).trim(), matcher.group(2).trim());
}
}
return where;
}
/**
* Parse SET clause
* Example: name='Alice', age='31'
*/
private static Map<String, String> parseSetClause(String setStr) {
Map<String, String> sets = new LinkedHashMap<>();
String[] assignments = setStr.split(",");
for (String assignment : assignments) {
assignment = assignment.trim();
Pattern pattern = Pattern.compile("([\\w]+)\\s*=\\s*['\"]?([^'\"]+)['\"]?");
Matcher matcher = pattern.matcher(assignment);
if (matcher.find()) {
sets.put(matcher.group(1).trim(), matcher.group(2).trim());
}
}
return sets;
}
/**
* Parse VALUES clause, handling quoted strings
*/
private static String[] parseValues(String valuesStr) {
List<String> values = new ArrayList<>();
StringBuilder current = new StringBuilder();
boolean inQuotes = false;
char quoteChar = '\0';
for (int i = 0; i < valuesStr.length(); i++) {
char c = valuesStr.charAt(i);
if ((c == '\'' || c == '"') && (i == 0 || valuesStr.charAt(i - 1) != '\\')) {
if (!inQuotes) {
inQuotes = true;
quoteChar = c;
} else if (c == quoteChar) {
inQuotes = false;
quoteChar = '\0';
} else {
current.append(c);
}
} else if (c == ',' && !inQuotes) {
values.add(current.toString().trim());
current = new StringBuilder();
} else if (!Character.isWhitespace(c) || inQuotes) {
current.append(c);
}
}
if (current.length() > 0) {
values.add(current.toString().trim());
}
return values.toArray(new String[0]);
}
}