|
1 | 1 | import java.sql.*;
|
2 |
| -import java.util.*; |
3 |
| -import java.util.regex.Matcher; |
4 |
| -import java.util.regex.Pattern; |
5 |
| -import java.util.stream.Collectors; |
| 2 | +import java.util.ArrayList; |
| 3 | +import java.util.HashMap; |
| 4 | +import java.util.List; |
| 5 | +import java.util.Map; |
6 | 6 |
|
7 | 7 | public class MemeLangQueryProcessor {
|
8 | 8 |
|
9 | 9 | // Database configuration constants
|
10 |
| - private static final String DB_TYPE = "sqlite3"; // Default to 'sqlite3'. Options: 'sqlite3', 'mysql', 'postgres' |
11 |
| - private static final String DB_PATH = "data.sqlite"; // Default path for SQLite3 |
12 |
| - private static final String DB_HOST = "localhost"; // Host for MySQL/Postgres |
13 |
| - private static final String DB_USER = "username"; // Username for MySQL/Postgres |
14 |
| - private static final String DB_PASSWORD = "password"; // Password for MySQL/Postgres |
15 |
| - private static final String DB_NAME = "database_name"; // Database name for MySQL/Postgres |
16 |
| - private static final String DB_TABLE = "meme"; // Default table name for queries |
| 10 | + private static final String DB_TYPE = System.getenv().getOrDefault("DB_TYPE", "sqlite3"); // Options: 'sqlite3', 'mysql', 'postgres' |
| 11 | + private static final String DB_PATH = System.getenv().getOrDefault("DB_PATH", "data.sqlite"); // Path for SQLite3 |
| 12 | + private static final String DB_HOST = System.getenv().getOrDefault("DB_HOST", "localhost"); // Host for MySQL/Postgres |
| 13 | + private static final String DB_USER = System.getenv().getOrDefault("DB_USER", "username"); // Username for MySQL/Postgres |
| 14 | + private static final String DB_PASSWORD = System.getenv().getOrDefault("DB_PASSWORD", "password"); // Password for MySQL/Postgres |
| 15 | + private static final String DB_NAME = System.getenv().getOrDefault("DB_NAME", "database_name"); // Database name for MySQL/Postgres |
| 16 | + private static final String DB_TABLE = System.getenv().getOrDefault("DB_TABLE", "meme"); // Default table name for queries |
17 | 17 |
|
18 |
| - public static void main(String[] args) { |
19 |
| - String memelangQuery = ".admire & .explore & :amsterdam | .letter:ord < 2 & :bangkok"; |
20 |
| - List<Map<String, Object>> results = memeQuery(memelangQuery); |
21 |
| - System.out.println(memeOut(results)); |
22 |
| - } |
| 18 | + private static Connection connection = null; |
23 | 19 |
|
24 | 20 | // Main function to process memelang query and return results
|
25 |
| - public static List<Map<String, Object>> memeQuery(String memelangQuery) { |
26 |
| - // Remove all whitespace from the input |
27 |
| - memelangQuery = memelangQuery.replaceAll("\\s+", ""); |
28 |
| - |
29 |
| - try { |
30 |
| - // Translate memelang to SQL |
31 |
| - String sqlQuery = memeSQL(memelangQuery); |
32 |
| - |
33 |
| - // Call the appropriate database function based on DB_TYPE constant |
34 |
| - switch (DB_TYPE) { |
35 |
| - case "sqlite3": |
36 |
| - return memeSQLite3(sqlQuery); |
37 |
| - case "mysql": |
38 |
| - return memeMySQL(sqlQuery); |
39 |
| - case "postgres": |
40 |
| - return memePostgres(sqlQuery); |
41 |
| - default: |
42 |
| - throw new IllegalArgumentException("Unsupported database type: " + DB_TYPE); |
43 |
| - } |
44 |
| - } catch (Exception e) { |
45 |
| - Map<String, Object> error = new HashMap<>(); |
46 |
| - error.put("error", e.getMessage()); |
47 |
| - return Collections.singletonList(error); |
48 |
| - } |
| 21 | + public static List<Map<String, Object>> memeQuery(String memelangQuery) throws SQLException { |
| 22 | + String sqlQuery = memeSQL(memelangQuery); |
| 23 | + return executeQuery(sqlQuery); |
49 | 24 | }
|
50 | 25 |
|
51 |
| - // Function to handle AND, OR conditions and translate to SQL |
| 26 | + // Generate SQL from the memelang query |
52 | 27 | public static String memeSQL(String query) {
|
53 |
| - // If there are multiple OR clauses, separate each one with a UNION and wrap each in a SELECT statement |
54 |
| - if (query.contains("|")) { |
55 |
| - String[] clauses = query.split("\\|"); |
56 |
| - List<String> sqlClauses = Arrays.stream(clauses) |
57 |
| - .map(clause -> "SELECT m.* FROM " + DB_TABLE + " m " + memeJunction(clause.trim())) |
58 |
| - .collect(Collectors.toList()); |
59 |
| - return String.join(" UNION ", sqlClauses); |
| 28 | + query = query.replaceAll("\\s+", ""); // Remove all whitespace |
| 29 | + |
| 30 | + // Split the query by | for OR conditions |
| 31 | + String[] orClauses = query.split("\\|"); |
| 32 | + StringBuilder sqlClauses = new StringBuilder(); |
| 33 | + for (String clause : orClauses) { |
| 34 | + if (sqlClauses.length() > 0) sqlClauses.append(" UNION "); |
| 35 | + sqlClauses.append(memeClause(clause.trim())); |
60 | 36 | }
|
| 37 | + return sqlClauses.toString(); |
| 38 | + } |
61 | 39 |
|
62 |
| - // If no OR, treat it as a single SELECT query |
63 |
| - return "SELECT m.* FROM " + DB_TABLE + " m " + memeJunction(query); |
| 40 | + private static String memeClause(String clause) { |
| 41 | + // Check for & in the clause for AND conditions |
| 42 | + if (clause.contains("&")) { |
| 43 | + return "SELECT m.* FROM " + DB_TABLE + " m " + memeJunction(clause); |
| 44 | + } else { |
| 45 | + // Handle simple clause with no & |
| 46 | + ParsedClause result = memeParse(clause); |
| 47 | + return "SELECT * FROM " + DB_TABLE + " WHERE " + result.clause; |
| 48 | + } |
64 | 49 | }
|
65 | 50 |
|
66 |
| - // Handle single clause logic for both AND (&) conditions and basic WHERE filtering |
67 |
| - public static String memeJunction(String query) { |
| 51 | + private static String memeJunction(String query) { |
68 | 52 | List<String> filters = new ArrayList<>();
|
| 53 | + List<String> havingConditions = new ArrayList<>(); |
69 | 54 |
|
70 |
| - // Handle AND conditions |
71 |
| - if (query.contains("&")) { |
72 |
| - String[] clauses = query.split("&"); |
73 |
| - List<String> havingConditions = new ArrayList<>(); |
| 55 | + // Split the query by '&' and process each clause |
| 56 | + String[] clauses = query.split("&"); |
| 57 | + for (String clause : clauses) { |
| 58 | + clause = clause.trim(); |
| 59 | + boolean isAndNotCondition = clause.startsWith("!"); |
| 60 | + if (isAndNotCondition) clause = clause.substring(1); |
74 | 61 |
|
75 |
| - for (String clause : clauses) { |
76 |
| - Map<String, String> result = memeParse(clause.trim()); |
77 |
| - havingConditions.add("SUM(CASE WHEN " + result.get("clause") + " THEN 1 ELSE 0 END) > 0"); |
78 |
| - if (!result.get("filter").isEmpty()) { |
79 |
| - filters.add("(" + result.get("filter") + ")"); |
80 |
| - } |
81 |
| - } |
| 62 | + ParsedClause result = memeParse(clause); |
| 63 | + havingConditions.add("SUM(CASE WHEN " + result.clause + " THEN 1 ELSE 0 END) " + |
| 64 | + (isAndNotCondition ? "= 0" : "> 0")); |
82 | 65 |
|
83 |
| - return "JOIN (SELECT aid FROM " + DB_TABLE + " GROUP BY aid HAVING " + |
84 |
| - String.join(" AND ", havingConditions) + ") AS aids ON m.aid = aids.aid" + |
85 |
| - (!filters.isEmpty() ? " WHERE " + String.join(" OR ", memeFilterGroup(filters)) : ""); |
| 66 | + if (result.filter != null && !result.filter.isEmpty()) { |
| 67 | + filters.add("(" + result.filter + ")"); |
| 68 | + } |
86 | 69 | }
|
87 | 70 |
|
88 |
| - // No AND, so it's a single WHERE condition |
89 |
| - Map<String, String> result = memeParse(query); |
90 |
| - if (!result.get("filter").isEmpty()) { |
91 |
| - filters.add("(" + result.get("filter") + ")"); |
92 |
| - } |
93 |
| - return "WHERE " + result.get("clause") + |
94 |
| - (!filters.isEmpty() ? " AND " + String.join(" OR ", memeFilterGroup(filters)) : ""); |
| 71 | + return "JOIN (SELECT aid FROM " + DB_TABLE + " GROUP BY aid HAVING " + |
| 72 | + String.join(" AND ", havingConditions) + ") AS aids ON m.aid = aids.aid" + |
| 73 | + (filters.isEmpty() ? "" : " WHERE " + String.join(" OR ", memeFilterGroup(filters))); |
95 | 74 | }
|
96 | 75 |
|
97 |
| - // Function to parse individual components of the memelang query |
98 |
| - public static Map<String, String> memeParse(String query) { |
99 |
| - String pattern = "^([A-Za-z0-9]*)\\.?([A-Za-z0-9]*):?([A-Za-z0-9]*)?([<>=#]*)?(-?\\d*\\.?\\d*)$"; |
100 |
| - Pattern regex = Pattern.compile(pattern); |
101 |
| - Matcher matcher = regex.matcher(query); |
102 |
| - |
103 |
| - if (matcher.find()) { |
104 |
| - String aid = matcher.group(1) != null ? matcher.group(1) : ""; |
105 |
| - String rid = matcher.group(2) != null ? matcher.group(2) : ""; |
106 |
| - String bid = matcher.group(3) != null ? matcher.group(3) : ""; |
107 |
| - String operator = matcher.group(4) != null ? matcher.group(4).replace("#=", "=") : "="; |
108 |
| - String qnt = !matcher.group(5).isEmpty() ? matcher.group(5) : "1"; |
| 76 | + private static ParsedClause memeParse(String query) { |
| 77 | + String pattern = "^([A-Za-z0-9]*)\\.([A-Za-z0-9]*):?([A-Za-z0-9]*)?([<>=#]*)?(-?\\d*\\.?\\d*)$"; |
| 78 | + if (query.matches(pattern)) { |
| 79 | + String[] parts = query.split("[:.#]"); |
| 80 | + String aid = parts.length > 0 ? parts[0] : null; |
| 81 | + String rid = parts.length > 1 ? parts[1] : null; |
| 82 | + String bid = parts.length > 2 ? parts[2] : null; |
| 83 | + String operator = parts.length > 3 ? parts[3].replace("#=", "=") : "="; |
| 84 | + String qnt = parts.length > 4 && !parts[4].isEmpty() ? parts[4] : "1"; |
109 | 85 |
|
110 | 86 | List<String> conditions = new ArrayList<>();
|
111 |
| - if (!aid.isEmpty()) conditions.add("aid='" + aid + "'"); |
112 |
| - if (!rid.isEmpty()) conditions.add("rid='" + rid + "'"); |
113 |
| - if (!bid.isEmpty()) conditions.add("bid='" + bid + "'"); |
| 87 | + if (aid != null) conditions.add("aid='" + aid + "'"); |
| 88 | + if (rid != null) conditions.add("rid='" + rid + "'"); |
| 89 | + if (bid != null) conditions.add("bid='" + bid + "'"); |
114 | 90 | conditions.add("qnt" + operator + qnt);
|
115 | 91 |
|
116 | 92 | List<String> filterConditions = new ArrayList<>();
|
117 |
| - if (!rid.isEmpty()) filterConditions.add("rid='" + rid + "'"); |
118 |
| - if (!bid.isEmpty()) filterConditions.add("bid='" + bid + "'"); |
| 93 | + if (rid != null) filterConditions.add("rid='" + rid + "'"); |
| 94 | + if (bid != null) filterConditions.add("bid='" + bid + "'"); |
119 | 95 |
|
120 |
| - Map<String, String> result = new HashMap<>(); |
121 |
| - result.put("clause", "(" + String.join(" AND ", conditions) + ")"); |
122 |
| - result.put("filter", String.join(" AND ", filterConditions)); |
123 |
| - return result; |
| 96 | + return new ParsedClause(String.join(" AND ", conditions), String.join(" AND ", filterConditions)); |
124 | 97 | } else {
|
125 | 98 | throw new IllegalArgumentException("Invalid memelang format: " + query);
|
126 | 99 | }
|
127 | 100 | }
|
128 | 101 |
|
129 |
| - // Group filters to reduce SQL complexity |
130 |
| - public static List<String> memeFilterGroup(List<String> filters) { |
| 102 | + private static List<String> memeFilterGroup(List<String> filters) { |
| 103 | + List<String> grouped = new ArrayList<>(); |
131 | 104 | List<String> ridValues = new ArrayList<>();
|
132 | 105 | List<String> bidValues = new ArrayList<>();
|
133 | 106 | List<String> complexFilters = new ArrayList<>();
|
134 | 107 |
|
135 | 108 | for (String filter : filters) {
|
136 |
| - Matcher ridMatcher = Pattern.compile("^\\(rid='([A-Za-z0-9]+)'\\)$").matcher(filter); |
137 |
| - Matcher bidMatcher = Pattern.compile("^\\(bid='([A-Za-z0-9]+)'\\)$").matcher(filter); |
138 |
| - |
139 |
| - if (ridMatcher.find()) { |
140 |
| - ridValues.add(ridMatcher.group(1)); |
141 |
| - } else if (bidMatcher.find()) { |
142 |
| - bidValues.add(bidMatcher.group(1)); |
| 109 | + if (filter.matches("\\(rid='([A-Za-z0-9]+)'\\)")) { |
| 110 | + ridValues.add(filter.split("=")[1].replace("'", "").replace(")", "")); |
| 111 | + } else if (filter.matches("\\(bid='([A-Za-z0-9]+)'\\)")) { |
| 112 | + bidValues.add(filter.split("=")[1].replace("'", "").replace(")", "")); |
143 | 113 | } else {
|
144 | 114 | complexFilters.add(filter);
|
145 | 115 | }
|
146 | 116 | }
|
147 | 117 |
|
148 |
| - List<String> grouped = new ArrayList<>(); |
149 |
| - if (!ridValues.isEmpty()) { |
150 |
| - grouped.add("m.rid IN ('" + String.join("','", ridValues) + "')"); |
151 |
| - } |
152 |
| - if (!bidValues.isEmpty()) { |
153 |
| - grouped.add("m.bid IN ('" + String.join("','", bidValues) + "')"); |
154 |
| - } |
| 118 | + if (!ridValues.isEmpty()) grouped.add("m.rid IN ('" + String.join("','", ridValues) + "')"); |
| 119 | + if (!bidValues.isEmpty()) grouped.add("m.bid IN ('" + String.join("','", bidValues) + "')"); |
155 | 120 |
|
156 | 121 | grouped.addAll(complexFilters);
|
157 | 122 | return grouped;
|
158 | 123 | }
|
159 | 124 |
|
160 |
| - // SQLite3 database query function |
161 |
| - public static List<Map<String, Object>> memeSQLite3(String sqlQuery) throws SQLException { |
162 |
| - try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + DB_PATH); |
163 |
| - Statement stmt = conn.createStatement(); |
164 |
| - ResultSet rs = stmt.executeQuery(sqlQuery)) { |
| 125 | + private static Connection getConnection() throws SQLException { |
| 126 | + if (connection == null || connection.isClosed()) { |
| 127 | + switch (DB_TYPE) { |
| 128 | + case "sqlite3": |
| 129 | + connection = DriverManager.getConnection("jdbc:sqlite:" + DB_PATH); |
| 130 | + break; |
| 131 | + case "mysql": |
| 132 | + connection = DriverManager.getConnection("jdbc:mysql://" + DB_HOST + "/" + DB_NAME, DB_USER, DB_PASSWORD); |
| 133 | + break; |
| 134 | + case "postgres": |
| 135 | + connection = DriverManager.getConnection("jdbc:postgresql://" + DB_HOST + "/" + DB_NAME, DB_USER, DB_PASSWORD); |
| 136 | + break; |
| 137 | + default: |
| 138 | + throw new SQLException("Unsupported database type: " + DB_TYPE); |
| 139 | + } |
| 140 | + } |
| 141 | + return connection; |
| 142 | + } |
| 143 | + |
| 144 | + private static List<Map<String, Object>> executeQuery(String sqlQuery) throws SQLException { |
| 145 | + List<Map<String, Object>> results = new ArrayList<>(); |
| 146 | + try (Connection conn = getConnection(); PreparedStatement stmt = conn.prepareStatement(sqlQuery); ResultSet rs = stmt.executeQuery()) { |
| 147 | + ResultSetMetaData metaData = rs.getMetaData(); |
| 148 | + int columnCount = metaData.getColumnCount(); |
165 | 149 |
|
166 |
| - List<Map<String, Object>> results = new ArrayList<>(); |
167 | 150 | while (rs.next()) {
|
168 |
| - ResultSetMetaData md = rs.getMetaData(); |
169 |
| - int columns = md.getColumnCount(); |
170 |
| - Map<String, Object> row = new HashMap<>(columns); |
171 |
| - for (int i = 1; i <= columns; i++) { |
172 |
| - row.put(md.getColumnName(i), rs.getObject(i)); |
| 151 | + Map<String, Object> row = new HashMap<>(); |
| 152 | + for (int i = 1; i <= columnCount; i++) { |
| 153 | + row.put(metaData.getColumnName(i), rs.getObject(i)); |
173 | 154 | }
|
174 | 155 | results.add(row);
|
175 | 156 | }
|
176 |
| - return results; |
177 | 157 | }
|
| 158 | + return results; |
178 | 159 | }
|
179 | 160 |
|
180 |
| - // MySQL database query function |
181 |
| - public static List<Map<String, Object>> memeMySQL(String sqlQuery) throws SQLException { |
182 |
| - try (Connection conn = DriverManager.getConnection("jdbc:mysql://" + DB_HOST + "/" + DB_NAME, DB_USER, DB_PASSWORD); |
183 |
| - Statement stmt = conn.createStatement(); |
184 |
| - ResultSet rs = stmt.executeQuery(sqlQuery)) { |
| 161 | + // Class to hold parsed clauses |
| 162 | + private static class ParsedClause { |
| 163 | + String clause; |
| 164 | + String filter; |
185 | 165 |
|
186 |
| - List<Map<String, Object>> results = new ArrayList<>(); |
187 |
| - while (rs.next()) { |
188 |
| - ResultSetMetaData md = rs.getMetaData(); |
189 |
| - int columns = md.getColumnCount(); |
190 |
| - Map<String, Object> row = new HashMap<>(columns); |
191 |
| - for (int i = 1; i <= columns; i++) { |
192 |
| - row.put(md.getColumnName(i), rs.getObject(i)); |
193 |
| - } |
194 |
| - results.add(row); |
| 166 | + ParsedClause(String clause, String filter) { |
| 167 | + this.clause = clause; |
| 168 | + this.filter = filter; |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + // Test function |
| 173 | + public static void main(String[] args) { |
| 174 | + List<String> queries = List.of( |
| 175 | + "ant.admire:amsterdam #= 0", |
| 176 | + "ant.believe:cairo", |
| 177 | + ".admire | .believe" |
| 178 | + // Add other test cases as needed |
| 179 | + ); |
| 180 | + |
| 181 | + for (String query : queries) { |
| 182 | + try { |
| 183 | + System.out.println("Memelang Query: " + query); |
| 184 | + String sqlQuery = memeSQL(query); |
| 185 | + System.out.println("Generated SQL: " + sqlQuery); |
| 186 | + List<Map<String, Object>> results = memeQuery(query); |
| 187 | + System.out.println("Results in Memelang Format:\n" + memeOut(results) + "\n"); |
| 188 | + } catch (Exception e) { |
| 189 | + System.err.println("Error: " + e.getMessage()); |
195 | 190 | }
|
196 |
| - return results; |
197 | 191 | }
|
198 | 192 | }
|
199 | 193 |
|
200 |
| - // PostgreSQL database query function |
201 |
| - public static List<Map<String, Object>> memePostgres(String sqlQuery) throws SQLException { |
202 |
| - String url = "jdbc:postgresql://" + DB_HOST + "/" + DB_NAME; |
203 |
| - try (Connection conn = DriverManager.getConnection(url, DB_USER, DB_PASSWORD); |
204 |
| - Statement stmt = conn.createStatement(); |
205 |
| - ResultSet rs = |
| 194 | + // Format results to Memelang format |
| 195 | + public static String memeOut(List<Map<String, Object>> results) { |
| 196 | + StringBuilder output = new StringBuilder(); |
| 197 | + for (Map<String, Object> row : results) { |
| 198 | + output.append(row.get("aid")).append(".").append(row.get("rid")).append(":").append(row.get("bid")) |
| 199 | + .append("=").append(row.get("qnt")).append(";\n"); |
| 200 | + } |
| 201 | + return output.toString(); |
| 202 | + } |
| 203 | +} |
0 commit comments