From b3c978e6fdd76b24e00f4ab6be907ca0265621a4 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Tue, 19 Aug 2025 03:24:10 +0200 Subject: [PATCH 01/44] Implemented PolyphenyConnection.java and QUeryExecutior.java Furthermore the .jar files were included, launch.json and settings.json were set up. Documentation still needs some last tweaks. --- PolyphenyConnection.java | 176 +++++++++++++++++++++++++++++++++++++++ QueryExecutor.java | 107 ++++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 PolyphenyConnection.java create mode 100644 QueryExecutor.java diff --git a/PolyphenyConnection.java b/PolyphenyConnection.java new file mode 100644 index 0000000..9966eac --- /dev/null +++ b/PolyphenyConnection.java @@ -0,0 +1,176 @@ +import java.sql.*; +import java.net.Socket; +import com.mathworks.engine.MatlabEngine; + +public class PolyphenyConnection { + private Connection connection; + private MatlabEngine matlabEngine; + private final String url, username, password; + + /* + @Description + - Constructor supporting lazy-open: Stores logins; connects on first use to protect server resources + + @params: + - url: the url of the database + - username: username to access the database with + - password: password to the corresponding username + + @return + - Creates a PolyphenyConnection object that can be passed to the ExecuteQuery class and used to run queries + */ + public PolyphenyConnection(String url, String username, String password) throws Exception { + this.url = url; + this.username = username; + this.password = password; + this.connection = null; //The connection is established later on when needed. Lazy open prevents accidental resource leaks induced by user + this.matlabEngine = null; //The Matlab engine is needed for the typecasting in ExecuteQuery later, but started here to avoid overhead + + StartLocalPolypheny(); //Starts Polypheny locally on the machine if it's not already running + StartMatlabEngine(); //Starts MatlabEngine to handle the query results. Every connection will each create their own MatlabEngine + } + + + // Checks if Polypheny is running locally and starts it if not + private boolean isPolyphenyRunning() throws Exception{ + try (Socket socket = new Socket("localhost", 20590)) { + return true; // Able to connect -> running, Otherwise an Exception will be thrown later + } + } + + + /* + @REQUIREMENTS: + - polypheny.jar in lib folder + + Starts the Polypheny application on the local machine + */ + public void StartLocalPolypheny() throws Exception{ + // If the connection isn't open locally start the Polypheny application locally. + if (!isPolyphenyRunning()) { + ProcessBuilder pb = new ProcessBuilder("java", "-jar", "lib/polypheny.jar"); + pb.inheritIO(); // show output + pb.start(); + Thread.sleep(4000); // wait for Polypheny to boot + } + // If the connection is open do nothing + else {System.err.println("Polypheny already running on local system"); + } + } + + + /* + @Description + - Starts the matlabEngine of the PolyphenyConnection class + + @param - + @return - + */ + public void StartMatlabEngine() throws Exception { + if (matlabEngine == null) { + matlabEngine = MatlabEngine.startMatlab(); + System.out.println("Shared MATLAB engine started."); + } + } + + /* + @Description + - Stops the matlabEngine of the PolyphenyConnection class + + @param - + @return - + */ + public void StopMatlabEngine() throws Exception { + if (matlabEngine != null) { + matlabEngine.close(); + matlabEngine = null; + System.out.println("Shared MATLAB engine stopped."); + } + } + + /* + @Description + Open the server connection to Polypheny if needed (reuse otherwise). Checking the if-clause in java is a lot faster for large numbers of + queries as it eliminates the ~10ms matlab-java crossover that opening and closing a connection from matlab would create. For 1M queries + that avoids 1M * 10ms = ~10 000 sec = 2.8 hrs of overhead. + + @param - + @return - + */ + public void openIfNeeded() { + if (connection == null) { + try { + Class.forName("org.polypheny.jdbc.PolyphenyDriver"); // load driver + connection = DriverManager.getConnection(url, username, password); //establish connection + } catch (Exception e) { + throw new RuntimeException("Failed to open connection", e); + } + } + } + + /* + @Description + Closes connection if open + + @param - + @return - + */ + public void close() { + try { + if (connection != null && !connection.isClosed()) { + connection.close(); + } + } catch (SQLException e) { + System.err.println("Failed to close connection: " + e.getMessage()); + } + + try { + if (matlabEngine!=null) matlabEngine.close(); + } catch(Exception e){ + System.err.println("Failed to close MATLAB: " + e.getMessage()); + } + } + + /* + @Description + Getter for the connection variable of PolyphenyConnection + + @param - + @return Connection connection of the PolyphenyConnection class + */ + public Connection get_connection() { + return this.connection; + } + + // Setter function for the connection variable + public void set_connection(Connection input_connection){ + this.connection = input_connection; + } + + // Getter function for the connection variable + public MatlabEngine get_MatlabEngine() { + return this.matlabEngine; + } + + +} + +/* +public static void main(String[] args) { + try { + String url = "jdbc:polypheny://localhost/public"; + String user = "pa"; + String pass = ""; + + PolyphenyConnection conn = new PolyphenyConnection(url, user, pass); + Object result = conn.execute("sql", "public", "SELECT * FROM emps;"); + + conn.matlab.eval("disp(head(T,5));"); + conn.close(); + + } catch (Exception e) { + e.printStackTrace(); + } +} +} + */ diff --git a/QueryExecutor.java b/QueryExecutor.java new file mode 100644 index 0000000..edc08f9 --- /dev/null +++ b/QueryExecutor.java @@ -0,0 +1,107 @@ +import java.sql.*; +import com.mathworks.engine.MatlabEngine; + +public class QueryExecutor{ + private PolyphenyConnection polyconnection; + + /* + @Description + - Constructor + + @param + - polyconnection: PolyphenyConnection object that holds the connection details to the Database. It's used to execute queries + @return + - Object of the query (SQL: empty, scalar or table; MongoQL: TODO; Cypher: TODO) + */ + public QueryExecutor(PolyphenyConnection polyconnection) { + this.polyconnection = polyconnection; + } + + + /* + @Description + - Executes the query depending on the language given by the user + + @param + - language: The database language that is used (e.g. SQL, MongoQL, Cypher) + - namespace: Name of the database namespace (e.g. emps, students) + - query: The query-text to be executed (e.g. FROM emps SELECT *) + @return + - ResultToMatlab(rs) which is a Matlab compatible object that is cast to the Matlab user. + */ + public Object execute(String language, String namespace, String query) { + + polyconnection.openIfNeeded(); + switch (language.toLowerCase()) { + default: + System.err.println("Unsupported language: " + language); + return null; + + case "sql": + try (Statement stmt = polyconnection.get_connection().createStatement(); + ResultSet rs = stmt.executeQuery(query)) + { + return ResultToMatlab(rs); + } catch (Exception e) { + System.err.println("SQL execution failed: " + e.getMessage()); + return null; + } + + case "mongoql": + throw new UnsupportedOperationException("MongoQL execution not yet implemented."); + + case "cypher": + throw new UnsupportedOperationException("Cypher execution not yet implemented."); + } + } + + // Executes multi-model queries (SQL, MongoQL, Cypher) + public Object ResultToMatlab(ResultSet rs) throws Exception { + + MatlabEngine engine = polyconnection.get_MatlabEngine(); + ResultSetMetaData meta = rs.getMetaData(); + int colCount = meta.getColumnCount(); + + // ───────────────────────────── + // Case 1: Empty Result + // ───────────────────────────── + if (!rs.next()) { + System.out.println("Empty result set."); + engine.eval("T = table();"); + return engine.getVariable("T"); + } + + // ───────────────────────────── + // Case 2: Scalar Result (1x1) + // ───────────────────────────── + if (colCount == 1 && rs.isLast()) { + Object scalar = rs.getObject(1); + engine.putVariable("scalarResult", scalar); + return engine.getVariable("scalarResult"); + } + + // ───────────────────────────── + // Case 3: Tabular Result (≥1 column, ≥1 row) + // ───────────────────────────── + String[] columnNames = new String[colCount]; + for (int i = 1; i <= colCount; i++) { + columnNames[i - 1] = meta.getColumnName(i); + } + + engine.eval("T = table();"); + engine.putVariable("colNames", columnNames); + engine.eval("T.Properties.VariableNames = colNames;"); + + // First row already fetched above with rs.next() + do { + Object[] rowData = new Object[colCount]; + for (int i = 1; i <= colCount; i++) { + rowData[i - 1] = rs.getObject(i); + } + engine.putVariable("rowData", rowData); + engine.eval("T = [T; cell2table(rowData, 'VariableNames', colNames)];"); + } while (rs.next()); + + return engine.getVariable("T"); +} +} From a09f1731c56724f74c363c5910fff6320c998162 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Thu, 21 Aug 2025 15:31:02 +0200 Subject: [PATCH 02/44] Finished documentation for PolyphenyConnection.java and QueryExecutor.java --- PolyphenyConnection.java | 43 ++++++++++++++++++++++++++++++---------- QueryExecutor.java | 10 ++++++++-- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/PolyphenyConnection.java b/PolyphenyConnection.java index 9966eac..ff43367 100644 --- a/PolyphenyConnection.java +++ b/PolyphenyConnection.java @@ -31,7 +31,14 @@ public PolyphenyConnection(String url, String username, String password) throws } - // Checks if Polypheny is running locally and starts it if not + /* + @Description + - Checks if Polypheny is running locally and starts it if not + + @param - + @return + - Boolean true or Exception: because Polypheny is either running in the end or was started. + */ private boolean isPolyphenyRunning() throws Exception{ try (Socket socket = new Socket("localhost", 20590)) { return true; // Able to connect -> running, Otherwise an Exception will be thrown later @@ -40,10 +47,14 @@ private boolean isPolyphenyRunning() throws Exception{ /* - @REQUIREMENTS: + @REQUIREMENTS - polypheny.jar in lib folder - Starts the Polypheny application on the local machine + @Description + - Starts the Polypheny application on the local machine + + @param - + @return - */ public void StartLocalPolypheny() throws Exception{ // If the connection isn't open locally start the Polypheny application locally. @@ -90,9 +101,9 @@ public void StopMatlabEngine() throws Exception { /* @Description - Open the server connection to Polypheny if needed (reuse otherwise). Checking the if-clause in java is a lot faster for large numbers of - queries as it eliminates the ~10ms matlab-java crossover that opening and closing a connection from matlab would create. For 1M queries - that avoids 1M * 10ms = ~10 000 sec = 2.8 hrs of overhead. + Opens the server connection to Polypheny if needed (reuse otherwise). Checking the if-clause in java is a lot faster, than iterative + opening and closing of the connection after every use for large numbers of queries, as it eliminates the ~10ms matlab-java crossover + that opening and closing a connection from matlab would create. For 1M queries that avoids 1M * 10ms = ~10 000 sec = 2.8 hrs of overhead. @param - @return - @@ -133,21 +144,33 @@ public void close() { /* @Description - Getter for the connection variable of PolyphenyConnection + Getter function for the connection variable of PolyphenyConnection @param - - @return Connection connection of the PolyphenyConnection class + @return Connection connection variable of the PolyphenyConnection class */ public Connection get_connection() { return this.connection; } - // Setter function for the connection variable + /* + @Description + Setter function for the connection variable + + @param input_connection: The connection we want to set your PolyphenyConnection object to. + @return - + */ public void set_connection(Connection input_connection){ this.connection = input_connection; } - // Getter function for the connection variable + /* + @Description + Getter function for the MatlabEngine of the PolyphenyConnection object + + @param - + @return MatlabEngine matlabEngine variable of the PolyphenyConnection class + */ public MatlabEngine get_MatlabEngine() { return this.matlabEngine; } diff --git a/QueryExecutor.java b/QueryExecutor.java index edc08f9..bba12f3 100644 --- a/QueryExecutor.java +++ b/QueryExecutor.java @@ -55,7 +55,13 @@ public Object execute(String language, String namespace, String query) { } } - // Executes multi-model queries (SQL, MongoQL, Cypher) + /* + @Description + Casts the result of the queries to MatlabObjects, depending on the Databse language (SQL, MongoQL, Cypher) + + @param ResultSet rs: The result object of the query + @return engine.getVariable("T") which is either null/scalar/table (SQL), document (MongoQL) or TODO (Cypher) + */ public Object ResultToMatlab(ResultSet rs) throws Exception { MatlabEngine engine = polyconnection.get_MatlabEngine(); @@ -72,7 +78,7 @@ public Object ResultToMatlab(ResultSet rs) throws Exception { } // ───────────────────────────── - // Case 2: Scalar Result (1x1) + // Case 2: Scalar Result // ───────────────────────────── if (colCount == 1 && rs.isLast()) { Object scalar = rs.getObject(1); From 3acda9b504e33df190d7b5d67806e5629a1cfa30 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Thu, 21 Aug 2025 21:12:57 +0200 Subject: [PATCH 03/44] Track full project from root, clean .gitignore, include .json --- .gitignore | 58 ++--- META-INF/extensions.idx | 1 + PolyphenyConnection.m | 86 ++++++++ README.md | 5 + src/main/java/.gitignore | 41 ++++ src/main/java/LICENSE | 201 ++++++++++++++++++ .../main/java/PolyphenyConnection.java | 70 +++--- .../main/java/QueryExecutor.java | 0 src/main/java/README.md | 2 + 9 files changed, 397 insertions(+), 67 deletions(-) create mode 100644 META-INF/extensions.idx create mode 100644 PolyphenyConnection.m create mode 100644 src/main/java/.gitignore create mode 100644 src/main/java/LICENSE rename PolyphenyConnection.java => src/main/java/PolyphenyConnection.java (77%) rename QueryExecutor.java => src/main/java/QueryExecutor.java (100%) create mode 100644 src/main/java/README.md diff --git a/.gitignore b/.gitignore index d4fb281..61ad799 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,17 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Linker files -*.ilk - -# Debugger Files -*.pdb - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -# debug information files -*.dwo +# Java build artifacts +*.class +*.log +out/ +build/ + +# VS Code settings (keep launch.json) +.vscode/ +!.vscode/launch.json +!.vscode/settings.json + +# OS-specific files +.DS_Store +Thumbs.db + +# Ignore lib folder except .jar files +lib/* \ No newline at end of file diff --git a/META-INF/extensions.idx b/META-INF/extensions.idx new file mode 100644 index 0000000..7aaba5e --- /dev/null +++ b/META-INF/extensions.idx @@ -0,0 +1 @@ +# Generated by PF4J diff --git a/PolyphenyConnection.m b/PolyphenyConnection.m new file mode 100644 index 0000000..ad3fc8b --- /dev/null +++ b/PolyphenyConnection.m @@ -0,0 +1,86 @@ +classdef Polypheny + properties + conn % JDBC Connection + opts % defaults for mode, formats, etc. + lastQueryStr % store last query to avoid re-preparing unnecessarily + lastStmt + end + + methods + function obj = Polypheny(url, user, pass, opts) + % constructor: JDBC connect goes here + % obj.conn = java.sql.DriverManager.getConnection(url, user, pass); + obj.opts = opts; + obj.lastQueryStr = ""; + obj.lastStmt = []; + end + + function obj = close(obj) + % JDBC close goes here + if ~isempty(obj.conn) + obj.conn.close(); + end + end + + function stmt = prepareStatement(obj, queryStr) + % Only prepare if query text has changed + % if-clause is way cheaper than preparing the new statement + if queryStr ~= obj.lastQueryStr + stmt = obj.conn.prepareStatement(queryStr); + obj.lastStmt = stmt; + obj.lastQueryStr = queryStr; + else + stmt = obj.lastStmt; + end + end + + function stmt = bindParameters(~, stmt, params) + % JDBC bind parameters + for i = 1:numel(params) + stmt.setObject(i, params{i}); + end + end + + function rs = executeQuery(~, stmt) + % Execute prepared statement + % Re-preparing unnecessarily can be magnitudes slower + rs = stmt.executeQuery(); + end + + function mode = lang2mode(~, language) + language = lower(string(language)); + switch language + case {"sql","postgres","ansi"} + mode = "relational"; + case {"mongo","mongoql","jsoniq"} + mode = "document"; + case {"cypher","gremlin","graph"} + mode = "graph"; + otherwise + mode = "relational"; % fallback + end + end + + function out = streamResults(~, rs, mode, varargin) + % Route results to correct parser + switch string(mode) + case "relational" + out = streamRelational(rs); + case "document" + out = streamDocument(rs, varargin{:}); + case "graph" + out = streamGraph(rs, varargin{:}); + otherwise + error("Unknown mode: %s", mode); + end + end + + function T = query(obj, language, queryStr, params, varargin) + mode = obj.lang2mode(language); + stmt = obj.prepareStatement(queryStr); + stmt = obj.bindParameters(stmt, params); + rs = obj.executeQuery(stmt); + T = obj.streamResults(rs, mode, varargin{:}); + end + end +end diff --git a/README.md b/README.md index 5a16b89..c7a6eaf 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # Matlab-Connector Addon for Matlab to connect and query a Polypheny database. + +@REQUIREMENTS: +polypheny.jar must be in the lib folder + +JDK17 (newer versions don't support the MatlabEngine yet https://www.mathworks.com/support/requirements/language-interfaces.html) \ No newline at end of file diff --git a/src/main/java/.gitignore b/src/main/java/.gitignore new file mode 100644 index 0000000..d4fb281 --- /dev/null +++ b/src/main/java/.gitignore @@ -0,0 +1,41 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Linker files +*.ilk + +# Debugger Files +*.pdb + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# debug information files +*.dwo diff --git a/src/main/java/LICENSE b/src/main/java/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/src/main/java/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PolyphenyConnection.java b/src/main/java/PolyphenyConnection.java similarity index 77% rename from PolyphenyConnection.java rename to src/main/java/PolyphenyConnection.java index ff43367..02ad942 100644 --- a/PolyphenyConnection.java +++ b/src/main/java/PolyphenyConnection.java @@ -39,9 +39,12 @@ public PolyphenyConnection(String url, String username, String password) throws @return - Boolean true or Exception: because Polypheny is either running in the end or was started. */ - private boolean isPolyphenyRunning() throws Exception{ + + private boolean isPolyphenyRunning() { try (Socket socket = new Socket("localhost", 20590)) { return true; // Able to connect -> running, Otherwise an Exception will be thrown later + } catch (Exception e) { + return false; } } @@ -56,17 +59,31 @@ private boolean isPolyphenyRunning() throws Exception{ @param - @return - */ - public void StartLocalPolypheny() throws Exception{ - // If the connection isn't open locally start the Polypheny application locally. + public void StartLocalPolypheny() { + try { if (!isPolyphenyRunning()) { + System.out.println("Polypheny not running. Attempting to start..."); ProcessBuilder pb = new ProcessBuilder("java", "-jar", "lib/polypheny.jar"); - pb.inheritIO(); // show output + pb.inheritIO(); pb.start(); - Thread.sleep(4000); // wait for Polypheny to boot - } - // If the connection is open do nothing - else {System.err.println("Polypheny already running on local system"); + + // Wait and retry a few times + int retries = 5; + int delay = 2000; // 2 seconds + while (retries-- > 0 && !isPolyphenyRunning()) { + System.out.println("Waiting for Polypheny to start..."); + Thread.sleep(delay); + } + + if (!isPolyphenyRunning()) { + System.err.println("Polypheny did not start in time."); + } + } else { + System.out.println("Polypheny is already running."); } + } catch (Exception e) { + System.err.println("Could not start Polypheny: " + e.getMessage()); + } } @@ -176,24 +193,25 @@ public MatlabEngine get_MatlabEngine() { } -} -/* -public static void main(String[] args) { - try { - String url = "jdbc:polypheny://localhost/public"; - String user = "pa"; - String pass = ""; - - PolyphenyConnection conn = new PolyphenyConnection(url, user, pass); - Object result = conn.execute("sql", "public", "SELECT * FROM emps;"); - - conn.matlab.eval("disp(head(T,5));"); - conn.close(); - - } catch (Exception e) { - e.printStackTrace(); - } + + public static void main(String[] args) { + try { + String url = "jdbc:polypheny://localhost/public"; + String user = "pa"; + String pass = ""; + + PolyphenyConnection conn = new PolyphenyConnection(url, user, pass); + QueryExecutor executor = new QueryExecutor(conn); + executor.execute("sql", "public", "SELECT * FROM emps;"); + + conn.get_MatlabEngine().eval("disp(head(T,5));"); + conn.close(); + + } catch (Exception e) { + e.printStackTrace(); + } } + + } - */ diff --git a/QueryExecutor.java b/src/main/java/QueryExecutor.java similarity index 100% rename from QueryExecutor.java rename to src/main/java/QueryExecutor.java diff --git a/src/main/java/README.md b/src/main/java/README.md new file mode 100644 index 0000000..5a16b89 --- /dev/null +++ b/src/main/java/README.md @@ -0,0 +1,2 @@ +# Matlab-Connector +Addon for Matlab to connect and query a Polypheny database. From 18bba05183304bca8965768544fc282da3227ef2 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Thu, 21 Aug 2025 21:51:42 +0200 Subject: [PATCH 04/44] Track full project from root, clean .gitignore, include .json --- src/main/java/.gitignore | 58 ++++++++------------------ src/main/java/PolyphenyConnection.java | 14 +++---- src/main/java/QueryExecutor.java | 2 +- 3 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/main/java/.gitignore b/src/main/java/.gitignore index d4fb281..61ad799 100644 --- a/src/main/java/.gitignore +++ b/src/main/java/.gitignore @@ -1,41 +1,17 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Linker files -*.ilk - -# Debugger Files -*.pdb - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -# debug information files -*.dwo +# Java build artifacts +*.class +*.log +out/ +build/ + +# VS Code settings (keep launch.json) +.vscode/ +!.vscode/launch.json +!.vscode/settings.json + +# OS-specific files +.DS_Store +Thumbs.db + +# Ignore lib folder except .jar files +lib/* \ No newline at end of file diff --git a/src/main/java/PolyphenyConnection.java b/src/main/java/PolyphenyConnection.java index 02ad942..032d830 100644 --- a/src/main/java/PolyphenyConnection.java +++ b/src/main/java/PolyphenyConnection.java @@ -118,9 +118,9 @@ public void StopMatlabEngine() throws Exception { /* @Description - Opens the server connection to Polypheny if needed (reuse otherwise). Checking the if-clause in java is a lot faster, than iterative - opening and closing of the connection after every use for large numbers of queries, as it eliminates the ~10ms matlab-java crossover - that opening and closing a connection from matlab would create. For 1M queries that avoids 1M * 10ms = ~10 000 sec = 2.8 hrs of overhead. + - Opens the server connection to Polypheny if needed (reuse otherwise). Checking the if-clause in java is a lot faster, than iterative + opening and closing of the connection after every use for large numbers of queries, as it eliminates the ~10ms matlab-java crossover + that opening and closing a connection from matlab would create. For 1M queries that avoids 1M * 10ms = ~10 000 sec = 2.8 hrs of overhead. @param - @return - @@ -138,7 +138,7 @@ public void openIfNeeded() { /* @Description - Closes connection if open + - Closes connection if open @param - @return - @@ -161,7 +161,7 @@ public void close() { /* @Description - Getter function for the connection variable of PolyphenyConnection + - Getter function for the connection variable of PolyphenyConnection @param - @return Connection connection variable of the PolyphenyConnection class @@ -172,7 +172,7 @@ public Connection get_connection() { /* @Description - Setter function for the connection variable + - Setter function for the connection variable @param input_connection: The connection we want to set your PolyphenyConnection object to. @return - @@ -183,7 +183,7 @@ public void set_connection(Connection input_connection){ /* @Description - Getter function for the MatlabEngine of the PolyphenyConnection object + - Getter function for the MatlabEngine of the PolyphenyConnection object @param - @return MatlabEngine matlabEngine variable of the PolyphenyConnection class diff --git a/src/main/java/QueryExecutor.java b/src/main/java/QueryExecutor.java index bba12f3..59cd08f 100644 --- a/src/main/java/QueryExecutor.java +++ b/src/main/java/QueryExecutor.java @@ -57,7 +57,7 @@ public Object execute(String language, String namespace, String query) { /* @Description - Casts the result of the queries to MatlabObjects, depending on the Databse language (SQL, MongoQL, Cypher) + - Casts the result of the queries to MatlabObjects, depending on the Databse language (SQL, MongoQL, Cypher) @param ResultSet rs: The result object of the query @return engine.getVariable("T") which is either null/scalar/table (SQL), document (MongoQL) or TODO (Cypher) From e9c61a94e40fd0fc402d9107c0c5d740dc31523f Mon Sep 17 00:00:00 2001 From: fygo97 Date: Thu, 21 Aug 2025 21:52:57 +0200 Subject: [PATCH 05/44] changed gitignore --- src/main/java/.gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/.gitignore b/src/main/java/.gitignore index 61ad799..8a7fffa 100644 --- a/src/main/java/.gitignore +++ b/src/main/java/.gitignore @@ -5,9 +5,9 @@ out/ build/ # VS Code settings (keep launch.json) -.vscode/ -!.vscode/launch.json -!.vscode/settings.json +#.vscode/ +#!.vscode/launch.json +#!.vscode/settings.json # OS-specific files .DS_Store From 67e79f0b722e08126ac3ffebba5c74aa3c223477 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Thu, 21 Aug 2025 21:55:09 +0200 Subject: [PATCH 06/44] changed gitignore --- src/main/java/.gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/.gitignore b/src/main/java/.gitignore index 8a7fffa..b733afd 100644 --- a/src/main/java/.gitignore +++ b/src/main/java/.gitignore @@ -5,9 +5,6 @@ out/ build/ # VS Code settings (keep launch.json) -#.vscode/ -#!.vscode/launch.json -#!.vscode/settings.json # OS-specific files .DS_Store From f373c76223bfdcc20ae72cfb664f347adfd59cbe Mon Sep 17 00:00:00 2001 From: fygo97 <101370056+fygo97@users.noreply.github.com> Date: Thu, 21 Aug 2025 22:05:54 +0200 Subject: [PATCH 07/44] Update .gitignore ->bugged locally --- .gitignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 61ad799..5bf8476 100644 --- a/.gitignore +++ b/.gitignore @@ -5,13 +5,10 @@ out/ build/ # VS Code settings (keep launch.json) -.vscode/ -!.vscode/launch.json -!.vscode/settings.json # OS-specific files .DS_Store Thumbs.db # Ignore lib folder except .jar files -lib/* \ No newline at end of file +lib/* From 0118158255c1e3f73a80df3001ff8ef9eee10c60 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Thu, 21 Aug 2025 22:06:48 +0200 Subject: [PATCH 08/44] Now I can finally push the .json files --- .vscode/launch.json | 13 +++++++++++++ .vscode/settings.json | 14 ++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b2ef4fa --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "PolyphenyConnection", + "request": "launch", + "mainClass": "PolyphenyConnection", + "projectName": "Learning Contract_7fe425cb", + "vmArgs": "-Djava.library.path=C:\\Programme\\MATLAB\\R2025a\\bin\\win64" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1bb05c7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "java.project.sourcePaths": ["src/main/java"], + "java.project.referencedLibraries": [ + "lib/engine.jar", + "lib/polypheny-jdbc-driver-2.3.jar", + "lib/polypheny.jar" + ], + "cSpell.words": [ + "emps", + "mongoql", + "polyconnection", + "Polypheny" + ] +} From 619b55cc42cc5fea2b57265cfb8bb5c1de6f12b5 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 23 Aug 2025 15:21:36 +0200 Subject: [PATCH 09/44] Added gradle build setup. Fixed JDBC Polypheny connection issue (listening to wrong port before). --- .gitattributes | 12 + .gitignore | 8 +- .vscode/launch.json | 13 +- .vscode/settings.json | 16 +- META-INF/extensions.idx | 1 - PolyphenyConnection.m | 86 ------ {src/main/java => app/bin/main}/.gitignore | 0 {src/main/java => app/bin/main}/LICENSE | 0 {src/main/java => app/bin/main}/README.md | 0 app/build.gradle | 42 +++ app/src/main/java/.gitignore | 14 + app/src/main/java/LICENSE | 201 ++++++++++++++ app/src/main/java/Main.java | 18 ++ .../src}/main/java/PolyphenyConnection.java | 74 +++--- {src => app/src}/main/java/QueryExecutor.java | 15 +- app/src/main/java/README.md | 2 + app/src/main/java/org/example/App.java | 14 + gradle.properties | 5 + gradle/libs.versions.toml | 10 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43705 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 251 ++++++++++++++++++ gradlew.bat | 94 +++++++ settings.gradle | 14 + 24 files changed, 758 insertions(+), 139 deletions(-) create mode 100644 .gitattributes delete mode 100644 META-INF/extensions.idx delete mode 100644 PolyphenyConnection.m rename {src/main/java => app/bin/main}/.gitignore (100%) rename {src/main/java => app/bin/main}/LICENSE (100%) rename {src/main/java => app/bin/main}/README.md (100%) create mode 100644 app/build.gradle create mode 100644 app/src/main/java/.gitignore create mode 100644 app/src/main/java/LICENSE create mode 100644 app/src/main/java/Main.java rename {src => app/src}/main/java/PolyphenyConnection.java (78%) rename {src => app/src}/main/java/QueryExecutor.java (92%) create mode 100644 app/src/main/java/README.md create mode 100644 app/src/main/java/org/example/App.java create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f91f646 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/.gitignore b/.gitignore index 5bf8476..f569ef0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,10 @@ build/ Thumbs.db # Ignore lib folder except .jar files -lib/* +libs/* + +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/.vscode/launch.json b/.vscode/launch.json index b2ef4fa..7b5cae5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,13 +1,24 @@ { "version": "0.2.0", "configurations": [ + { + "type": "java", + "name": "Main", + "request": "launch", + "mainClass": "Main", + "projectName": "Learning Contract_7fe425cb" + }, { "type": "java", "name": "PolyphenyConnection", "request": "launch", "mainClass": "PolyphenyConnection", "projectName": "Learning Contract_7fe425cb", - "vmArgs": "-Djava.library.path=C:\\Programme\\MATLAB\\R2025a\\bin\\win64" + "vmArgs": "-Djava.library.path=C:\\Programme\\MATLAB\\R2025a\\bin\\win64", + "env": { + "PATH": "C:\\Programme\\MATLAB\\R2025a\\bin\\win64;C:\\Programme\\MATLAB\\R2025a\\extern\\bin\\win64;${env:PATH}" + }, + "console": "internalConsole" } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 1bb05c7..25e6e67 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,4 @@ { - "java.project.sourcePaths": ["src/main/java"], - "java.project.referencedLibraries": [ - "lib/engine.jar", - "lib/polypheny-jdbc-driver-2.3.jar", - "lib/polypheny.jar" - ], - "cSpell.words": [ - "emps", - "mongoql", - "polyconnection", - "Polypheny" - ] -} + "java.project.sourcePaths": ["app/src/main/java"], + "java.project.referencedLibraries": [] +} \ No newline at end of file diff --git a/META-INF/extensions.idx b/META-INF/extensions.idx deleted file mode 100644 index 7aaba5e..0000000 --- a/META-INF/extensions.idx +++ /dev/null @@ -1 +0,0 @@ -# Generated by PF4J diff --git a/PolyphenyConnection.m b/PolyphenyConnection.m deleted file mode 100644 index ad3fc8b..0000000 --- a/PolyphenyConnection.m +++ /dev/null @@ -1,86 +0,0 @@ -classdef Polypheny - properties - conn % JDBC Connection - opts % defaults for mode, formats, etc. - lastQueryStr % store last query to avoid re-preparing unnecessarily - lastStmt - end - - methods - function obj = Polypheny(url, user, pass, opts) - % constructor: JDBC connect goes here - % obj.conn = java.sql.DriverManager.getConnection(url, user, pass); - obj.opts = opts; - obj.lastQueryStr = ""; - obj.lastStmt = []; - end - - function obj = close(obj) - % JDBC close goes here - if ~isempty(obj.conn) - obj.conn.close(); - end - end - - function stmt = prepareStatement(obj, queryStr) - % Only prepare if query text has changed - % if-clause is way cheaper than preparing the new statement - if queryStr ~= obj.lastQueryStr - stmt = obj.conn.prepareStatement(queryStr); - obj.lastStmt = stmt; - obj.lastQueryStr = queryStr; - else - stmt = obj.lastStmt; - end - end - - function stmt = bindParameters(~, stmt, params) - % JDBC bind parameters - for i = 1:numel(params) - stmt.setObject(i, params{i}); - end - end - - function rs = executeQuery(~, stmt) - % Execute prepared statement - % Re-preparing unnecessarily can be magnitudes slower - rs = stmt.executeQuery(); - end - - function mode = lang2mode(~, language) - language = lower(string(language)); - switch language - case {"sql","postgres","ansi"} - mode = "relational"; - case {"mongo","mongoql","jsoniq"} - mode = "document"; - case {"cypher","gremlin","graph"} - mode = "graph"; - otherwise - mode = "relational"; % fallback - end - end - - function out = streamResults(~, rs, mode, varargin) - % Route results to correct parser - switch string(mode) - case "relational" - out = streamRelational(rs); - case "document" - out = streamDocument(rs, varargin{:}); - case "graph" - out = streamGraph(rs, varargin{:}); - otherwise - error("Unknown mode: %s", mode); - end - end - - function T = query(obj, language, queryStr, params, varargin) - mode = obj.lang2mode(language); - stmt = obj.prepareStatement(queryStr); - stmt = obj.bindParameters(stmt, params); - rs = obj.executeQuery(stmt); - T = obj.streamResults(rs, mode, varargin{:}); - end - end -end diff --git a/src/main/java/.gitignore b/app/bin/main/.gitignore similarity index 100% rename from src/main/java/.gitignore rename to app/bin/main/.gitignore diff --git a/src/main/java/LICENSE b/app/bin/main/LICENSE similarity index 100% rename from src/main/java/LICENSE rename to app/bin/main/LICENSE diff --git a/src/main/java/README.md b/app/bin/main/README.md similarity index 100% rename from src/main/java/README.md rename to app/bin/main/README.md diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..4355a09 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,42 @@ +plugins { + id 'application' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation files('libs/engine.jar','libs/jmi.jar','libs/polypheny-jdbc-driver-2.3.jar') + // pick up every jar in ../libs + implementation fileTree(dir: "${rootProject.projectDir}/libs", include: ["*.jar"]) +} + +application { + mainClass = 'Main' // or whatever your main class is +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.named("run", JavaExec).configure { workingDir = rootProject.projectDir } + +def matlabCandidates = [ + "C:/Programme/MATLAB/R2025a/bin/win64", // German install path + "C:/Program Files/MATLAB/R2025a/bin/win64" // English install path +].findAll { file(it).exists() } + +tasks.named("run", JavaExec).configure { + doFirst { + def newPath = System.getenv("PATH") + matlabCandidates.each { newPath += ";" + it } + environment "PATH", newPath + if (!matlabCandidates.isEmpty()) { + jvmArgs "-Djava.library.path=" + matlabCandidates.join(";") + } + println "MATLAB native dirs: " + (matlabCandidates.isEmpty() ? "" : matlabCandidates) + } +} \ No newline at end of file diff --git a/app/src/main/java/.gitignore b/app/src/main/java/.gitignore new file mode 100644 index 0000000..b733afd --- /dev/null +++ b/app/src/main/java/.gitignore @@ -0,0 +1,14 @@ +# Java build artifacts +*.class +*.log +out/ +build/ + +# VS Code settings (keep launch.json) + +# OS-specific files +.DS_Store +Thumbs.db + +# Ignore lib folder except .jar files +lib/* \ No newline at end of file diff --git a/app/src/main/java/LICENSE b/app/src/main/java/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/app/src/main/java/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/app/src/main/java/Main.java b/app/src/main/java/Main.java new file mode 100644 index 0000000..007d42c --- /dev/null +++ b/app/src/main/java/Main.java @@ -0,0 +1,18 @@ +public class Main { + public static void main(String[] args) { + try { + String url = "jdbc:polypheny://localhost/public"; + String user = "pa"; + String pass = ""; + + PolyphenyConnection conn = new PolyphenyConnection(url, user, pass); + QueryExecutor executor = new QueryExecutor(conn); + executor.execute("sql", "public", "SELECT * FROM emps;"); + + conn.get_MatlabEngine().eval("disp(head(T,5));"); + conn.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/PolyphenyConnection.java b/app/src/main/java/PolyphenyConnection.java similarity index 78% rename from src/main/java/PolyphenyConnection.java rename to app/src/main/java/PolyphenyConnection.java index 032d830..7f52ea4 100644 --- a/src/main/java/PolyphenyConnection.java +++ b/app/src/main/java/PolyphenyConnection.java @@ -1,4 +1,5 @@ import java.sql.*; +import java.io.IOException; import java.net.Socket; import com.mathworks.engine.MatlabEngine; @@ -27,6 +28,7 @@ public PolyphenyConnection(String url, String username, String password) throws this.matlabEngine = null; //The Matlab engine is needed for the typecasting in ExecuteQuery later, but started here to avoid overhead StartLocalPolypheny(); //Starts Polypheny locally on the machine if it's not already running + waitForPolyphenyReady(); StartMatlabEngine(); //Starts MatlabEngine to handle the query results. Every connection will each create their own MatlabEngine } @@ -48,6 +50,29 @@ private boolean isPolyphenyRunning() { } } + private void waitForPolyphenyReady() { + int timeoutTime = 30; + int timeWaited = 0; + + while (timeWaited < timeoutTime) { + try (Socket socket = new Socket("127.0.0.1", 20590)) { + System.out.println("Polypheny is ready."); + return; + } catch (IOException e) { + System.out.println(" Waiting for Polypheny to become ready..."); + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for Polypheny."); + } + timeWaited++; + } + } + + throw new RuntimeException("Polypheny did not start within timeout."); + } + /* @REQUIREMENTS @@ -62,22 +87,18 @@ private boolean isPolyphenyRunning() { public void StartLocalPolypheny() { try { if (!isPolyphenyRunning()) { - System.out.println("Polypheny not running. Attempting to start..."); - ProcessBuilder pb = new ProcessBuilder("java", "-jar", "lib/polypheny.jar"); + System.out.println("Polypheny not running. Attempting to start Polypheny application"); + ProcessBuilder pb = new ProcessBuilder("java", "-jar", "libs/polypheny.jar"); pb.inheritIO(); pb.start(); - // Wait and retry a few times - int retries = 5; - int delay = 2000; // 2 seconds - while (retries-- > 0 && !isPolyphenyRunning()) { - System.out.println("Waiting for Polypheny to start..."); - Thread.sleep(delay); + // Wait until JDBC is ready + while (!isJdbcAvailable("jdbc:polypheny://localhost:20590", "pa", "")) { + System.out.println("Waiting for JDBC to become available..."); + Thread.sleep(1000); } - if (!isPolyphenyRunning()) { - System.err.println("Polypheny did not start in time."); - } + System.out.println("Polypheny JDBC is ready."); } else { System.out.println("Polypheny is already running."); } @@ -86,6 +107,15 @@ public void StartLocalPolypheny() { } } + private boolean isJdbcAvailable(String jdbcUrl, String user, String pass) { + try (java.sql.Connection conn = java.sql.DriverManager.getConnection(jdbcUrl, user, pass)) { + return conn != null && !conn.isClosed(); + } catch (Exception e) { + return false; + } + } + + /* @Description @@ -192,26 +222,4 @@ public MatlabEngine get_MatlabEngine() { return this.matlabEngine; } - - - - public static void main(String[] args) { - try { - String url = "jdbc:polypheny://localhost/public"; - String user = "pa"; - String pass = ""; - - PolyphenyConnection conn = new PolyphenyConnection(url, user, pass); - QueryExecutor executor = new QueryExecutor(conn); - executor.execute("sql", "public", "SELECT * FROM emps;"); - - conn.get_MatlabEngine().eval("disp(head(T,5));"); - conn.close(); - - } catch (Exception e) { - e.printStackTrace(); - } -} - - } diff --git a/src/main/java/QueryExecutor.java b/app/src/main/java/QueryExecutor.java similarity index 92% rename from src/main/java/QueryExecutor.java rename to app/src/main/java/QueryExecutor.java index 59cd08f..3b95ab8 100644 --- a/src/main/java/QueryExecutor.java +++ b/app/src/main/java/QueryExecutor.java @@ -89,13 +89,14 @@ public Object ResultToMatlab(ResultSet rs) throws Exception { // ───────────────────────────── // Case 3: Tabular Result (≥1 column, ≥1 row) // ───────────────────────────── - String[] columnNames = new String[colCount]; + String[] colNames = new String[colCount]; + for (int i = 1; i <= colCount; i++) { - columnNames[i - 1] = meta.getColumnName(i); + colNames[i - 1] = meta.getColumnName(i); } - + engine.eval("T = table();"); - engine.putVariable("colNames", columnNames); + engine.putVariable("colNames", colNames); engine.eval("T.Properties.VariableNames = colNames;"); // First row already fetched above with rs.next() @@ -105,6 +106,12 @@ public Object ResultToMatlab(ResultSet rs) throws Exception { rowData[i - 1] = rs.getObject(i); } engine.putVariable("rowData", rowData); + + if (colNames.length != rowData.length) { + throw new RuntimeException("Mismatch: colNames and rowData column count don't match"); + } + + engine.eval("T = [T; cell2table(rowData, 'VariableNames', colNames)];"); } while (rs.next()); diff --git a/app/src/main/java/README.md b/app/src/main/java/README.md new file mode 100644 index 0000000..5a16b89 --- /dev/null +++ b/app/src/main/java/README.md @@ -0,0 +1,2 @@ +# Matlab-Connector +Addon for Matlab to connect and query a Polypheny database. diff --git a/app/src/main/java/org/example/App.java b/app/src/main/java/org/example/App.java new file mode 100644 index 0000000..e7e1af9 --- /dev/null +++ b/app/src/main/java/org/example/App.java @@ -0,0 +1,14 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example; + +public class App { + public String getGreeting() { + return "Hello World!"; + } + + public static void main(String[] args) { + System.out.println(new App().getGreeting()); + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..377538c --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +org.gradle.configuration-cache=true + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..afde022 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,10 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +guava = "33.3.1-jre" +junit-jupiter = "5.11.3" + +[libraries] +guava = { module = "com.google.guava:guava", version.ref = "guava" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..9bbc975c742b298b441bfb90dbc124400a3751b9 GIT binary patch literal 43705 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 DHx0+F literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37f853b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..faf9300 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..7821301 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.13/userguide/multi_project_builds.html in the Gradle documentation. + */ + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0' +} + +rootProject.name = 'Learning Contract' +include('app') From 7007826364be6caee5098a3ef796b356991b0820 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Mon, 25 Aug 2025 03:50:36 +0200 Subject: [PATCH 10/44] Removed the matlab.engine approach. Goal will be to handle the result conversion from Java type to Matlab type in a small matlab wrapper because matlab.engine can't properly and reliably inject variables into the users matlab session. So I need to handle it in a wrapper. Also fixed several tiny mistakes and modified QueryExecutor.java in how it handles the results. --- .vscode/settings.json | 9 +++- app/src/main/java/PolyphenyConnection.java | 52 -------------------- app/src/main/java/QueryExecutor.java | 56 +++++++++++----------- 3 files changed, 36 insertions(+), 81 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 25e6e67..2801afd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,11 @@ { "java.project.sourcePaths": ["app/src/main/java"], - "java.project.referencedLibraries": [] + "java.project.referencedLibraries": [], + "cSpell.words": [ + "emps", + "JDBC", + "mongoql", + "polyconnection", + "Polypheny" + ] } \ No newline at end of file diff --git a/app/src/main/java/PolyphenyConnection.java b/app/src/main/java/PolyphenyConnection.java index 7f52ea4..14206ab 100644 --- a/app/src/main/java/PolyphenyConnection.java +++ b/app/src/main/java/PolyphenyConnection.java @@ -1,11 +1,9 @@ import java.sql.*; import java.io.IOException; import java.net.Socket; -import com.mathworks.engine.MatlabEngine; public class PolyphenyConnection { private Connection connection; - private MatlabEngine matlabEngine; private final String url, username, password; /* @@ -25,11 +23,8 @@ public PolyphenyConnection(String url, String username, String password) throws this.username = username; this.password = password; this.connection = null; //The connection is established later on when needed. Lazy open prevents accidental resource leaks induced by user - this.matlabEngine = null; //The Matlab engine is needed for the typecasting in ExecuteQuery later, but started here to avoid overhead - StartLocalPolypheny(); //Starts Polypheny locally on the machine if it's not already running waitForPolyphenyReady(); - StartMatlabEngine(); //Starts MatlabEngine to handle the query results. Every connection will each create their own MatlabEngine } @@ -115,37 +110,6 @@ private boolean isJdbcAvailable(String jdbcUrl, String user, String pass) { } } - - - /* - @Description - - Starts the matlabEngine of the PolyphenyConnection class - - @param - - @return - - */ - public void StartMatlabEngine() throws Exception { - if (matlabEngine == null) { - matlabEngine = MatlabEngine.startMatlab(); - System.out.println("Shared MATLAB engine started."); - } - } - - /* - @Description - - Stops the matlabEngine of the PolyphenyConnection class - - @param - - @return - - */ - public void StopMatlabEngine() throws Exception { - if (matlabEngine != null) { - matlabEngine.close(); - matlabEngine = null; - System.out.println("Shared MATLAB engine stopped."); - } - } - /* @Description - Opens the server connection to Polypheny if needed (reuse otherwise). Checking the if-clause in java is a lot faster, than iterative @@ -181,12 +145,6 @@ public void close() { } catch (SQLException e) { System.err.println("Failed to close connection: " + e.getMessage()); } - - try { - if (matlabEngine!=null) matlabEngine.close(); - } catch(Exception e){ - System.err.println("Failed to close MATLAB: " + e.getMessage()); - } } /* @@ -211,15 +169,5 @@ public void set_connection(Connection input_connection){ this.connection = input_connection; } - /* - @Description - - Getter function for the MatlabEngine of the PolyphenyConnection object - - @param - - @return MatlabEngine matlabEngine variable of the PolyphenyConnection class - */ - public MatlabEngine get_MatlabEngine() { - return this.matlabEngine; - } } diff --git a/app/src/main/java/QueryExecutor.java b/app/src/main/java/QueryExecutor.java index 3b95ab8..a01defb 100644 --- a/app/src/main/java/QueryExecutor.java +++ b/app/src/main/java/QueryExecutor.java @@ -1,5 +1,6 @@ import java.sql.*; -import com.mathworks.engine.MatlabEngine; +import java.util.ArrayList; +import java.util.List; public class QueryExecutor{ private PolyphenyConnection polyconnection; @@ -29,7 +30,7 @@ public QueryExecutor(PolyphenyConnection polyconnection) { @return - ResultToMatlab(rs) which is a Matlab compatible object that is cast to the Matlab user. */ - public Object execute(String language, String namespace, String query) { + public Object execute(String language, String query) { polyconnection.openIfNeeded(); switch (language.toLowerCase()) { @@ -57,64 +58,63 @@ public Object execute(String language, String namespace, String query) { /* @Description - - Casts the result of the queries to MatlabObjects, depending on the Databse language (SQL, MongoQL, Cypher) + - Casts the result of the queries to MatlabObjects, depending on the Database language (SQL, MongoQL, Cypher) @param ResultSet rs: The result object of the query - @return engine.getVariable("T") which is either null/scalar/table (SQL), document (MongoQL) or TODO (Cypher) + @return engine.getVariable("MatlabTable") which is either null/scalar/table (SQL), document (MongoQL) or TODO (Cypher) */ public Object ResultToMatlab(ResultSet rs) throws Exception { - MatlabEngine engine = polyconnection.get_MatlabEngine(); ResultSetMetaData meta = rs.getMetaData(); int colCount = meta.getColumnCount(); + Object Result; + Object[][] ResultArray; // ───────────────────────────── // Case 1: Empty Result // ───────────────────────────── if (!rs.next()) { System.out.println("Empty result set."); - engine.eval("T = table();"); - return engine.getVariable("T"); + Result = null; + return Result; } // ───────────────────────────── // Case 2: Scalar Result // ───────────────────────────── if (colCount == 1 && rs.isLast()) { + System.out.println("Scalar result set."); Object scalar = rs.getObject(1); - engine.putVariable("scalarResult", scalar); - return engine.getVariable("scalarResult"); + return scalar; } // ───────────────────────────── // Case 3: Tabular Result (≥1 column, ≥1 row) // ───────────────────────────── - String[] colNames = new String[colCount]; + String[] colNames = new String[colCount]; //get the column names to name the columns later for (int i = 1; i <= colCount; i++) { - colNames[i - 1] = meta.getColumnName(i); + colNames[i - 1] = meta.getColumnName(i); //assign the column names to the array } - - engine.eval("T = table();"); - engine.putVariable("colNames", colNames); - engine.eval("T.Properties.VariableNames = colNames;"); - // First row already fetched above with rs.next() + + List rows = new ArrayList<>(); // List of arrays to store the rows returned by the query do { - Object[] rowData = new Object[colCount]; - for (int i = 1; i <= colCount; i++) { - rowData[i - 1] = rs.getObject(i); - } - engine.putVariable("rowData", rowData); - - if (colNames.length != rowData.length) { - throw new RuntimeException("Mismatch: colNames and rowData column count don't match"); + Object[] row = new Object[colCount]; // Creates new array that will store the queries entries + for (int i=0; i Date: Mon, 25 Aug 2025 05:10:51 +0200 Subject: [PATCH 11/44] Added a quick test file. The java classes successfully and automatically handle the polypheny application, polypheny server connection aswell as the query execution. Queries are returned properly to java. --- app/build.gradle | 2 +- app/src/main/java/Main.java | 4 +-- app/src/main/java/QuickTest.java | 46 +++++++++++++++++++++++++++++++ app/src/main/java/execeuteQuery.m | 1 + 4 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/QuickTest.java create mode 100644 app/src/main/java/execeuteQuery.m diff --git a/app/build.gradle b/app/build.gradle index 4355a09..5e44188 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,7 +13,7 @@ dependencies { } application { - mainClass = 'Main' // or whatever your main class is + mainClass = 'QuickTest' // or whatever your main class is } java { diff --git a/app/src/main/java/Main.java b/app/src/main/java/Main.java index 007d42c..a2697d8 100644 --- a/app/src/main/java/Main.java +++ b/app/src/main/java/Main.java @@ -7,9 +7,7 @@ public static void main(String[] args) { PolyphenyConnection conn = new PolyphenyConnection(url, user, pass); QueryExecutor executor = new QueryExecutor(conn); - executor.execute("sql", "public", "SELECT * FROM emps;"); - - conn.get_MatlabEngine().eval("disp(head(T,5));"); + executor.execute("sql", "SELECT * FROM emps;"); conn.close(); } catch (Exception e) { e.printStackTrace(); diff --git a/app/src/main/java/QuickTest.java b/app/src/main/java/QuickTest.java new file mode 100644 index 0000000..3c16298 --- /dev/null +++ b/app/src/main/java/QuickTest.java @@ -0,0 +1,46 @@ +import java.util.Arrays; + +public class QuickTest { + public static void main(String[] args) throws Exception { + PolyphenyConnection conn = + new PolyphenyConnection("jdbc:polypheny://localhost:20590", "pa", ""); + try { + QueryExecutor exec = new QueryExecutor(conn); + + // 1) Scalar smoke test + Object r1 = exec.execute("sql", "SELECT 1 AS x"); + System.out.println("Scalar result: " + r1); + + // 2) Table smoke test + Object r2 = exec.execute("sql", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4"); + printTable(r2); + + // 3) First row from emps (1-row table) + Object r3 = exec.execute("sql", "SELECT * FROM emps LIMIT 1"); + System.out.println("First row from emps:"); + printTable(r3); + + // 4) Scalar from emps + Object r4 = exec.execute("sql", "SELECT empid FROM emps LIMIT 1"); + System.out.println("First empid (scalar): " + r4); + + } finally { + conn.close(); + System.exit(0); + } + } + + static void printTable(Object r) { + if (r instanceof Object[]) { + Object[] t = (Object[]) r; // { colNames, data } + String[] cols = (String[]) t[0]; + Object[][] data = (Object[][]) t[1]; + System.out.println("Cols: " + Arrays.toString(cols)); + for (Object[] row : data) { + System.out.println(Arrays.toString(row)); + } + } else { + System.out.println(String.valueOf(r)); + } + } +} diff --git a/app/src/main/java/execeuteQuery.m b/app/src/main/java/execeuteQuery.m new file mode 100644 index 0000000..a4e0de2 --- /dev/null +++ b/app/src/main/java/execeuteQuery.m @@ -0,0 +1 @@ +// Matlab wrapper goes here \ No newline at end of file From ca7672d197a1fc1ec0e63b4ea28983599ba8396f Mon Sep 17 00:00:00 2001 From: fygo97 Date: Wed, 27 Aug 2025 08:48:16 +0200 Subject: [PATCH 12/44] Added the IntelliJ codestyle xml by exporting it to Eclipse file and including it in the settings.json so VS Code uses it to format. Also deleted unnecessary .jar files. --- .vscode/Polypheny-Style.xml | 380 +++++++++++++++++++++ .vscode/settings.json | 15 +- app/build.gradle | 46 +-- app/src/main/java/PolyphenyConnection.java | 211 ++++++------ app/src/main/java/QueryExecutor.java | 126 +++---- app/src/main/java/QuickTest.java | 5 +- 6 files changed, 589 insertions(+), 194 deletions(-) create mode 100644 .vscode/Polypheny-Style.xml diff --git a/.vscode/Polypheny-Style.xml b/.vscode/Polypheny-Style.xml new file mode 100644 index 0000000..e8365c4 --- /dev/null +++ b/.vscode/Polypheny-Style.xml @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 2801afd..df6cbc8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { - "java.project.sourcePaths": ["app/src/main/java"], + "java.project.sourcePaths": [ + "app/src/main/java" + ], "java.project.referencedLibraries": [], "cSpell.words": [ "emps", @@ -7,5 +9,12 @@ "mongoql", "polyconnection", "Polypheny" - ] -} \ No newline at end of file + ], + "java.format.enabled": true, + "java.format.settings.url": ".vscode/Polypheny-Style.xml", + "java.format.settings.profile": "Polypheny Code Style", + "[java]": { + "editor.defaultFormatter": "redhat.java", + "editor.formatOnSave": true + } +} diff --git a/app/build.gradle b/app/build.gradle index 5e44188..0da44a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,42 +1,26 @@ plugins { - id 'application' + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' //necessary plugin to create the .jar file for matlab later } -repositories { - mavenCentral() -} +application { mainClass = 'QuickTest' } // currently running QuickTest.java as application + +repositories { mavenCentral() } dependencies { - implementation files('libs/engine.jar','libs/jmi.jar','libs/polypheny-jdbc-driver-2.3.jar') - // pick up every jar in ../libs - implementation fileTree(dir: "${rootProject.projectDir}/libs", include: ["*.jar"]) + // Pull all JARs from the **root** libs/ folder ( + implementation fileTree(dir: "${rootProject.projectDir}/libs", include: ["*.jar"]) + + // Silences SLF4J warnings + implementation 'org.slf4j:slf4j-simple:2.0.16' } -application { - mainClass = 'QuickTest' // or whatever your main class is +shadowJar { + archiveBaseName.set('polypheny') + archiveClassifier.set('all') + archiveVersion.set('') // => app/build/libs/polypheny-all.jar } java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + toolchain { languageVersion = JavaLanguageVersion.of(17) } } - -tasks.named("run", JavaExec).configure { workingDir = rootProject.projectDir } - -def matlabCandidates = [ - "C:/Programme/MATLAB/R2025a/bin/win64", // German install path - "C:/Program Files/MATLAB/R2025a/bin/win64" // English install path -].findAll { file(it).exists() } - -tasks.named("run", JavaExec).configure { - doFirst { - def newPath = System.getenv("PATH") - matlabCandidates.each { newPath += ";" + it } - environment "PATH", newPath - if (!matlabCandidates.isEmpty()) { - jvmArgs "-Djava.library.path=" + matlabCandidates.join(";") - } - println "MATLAB native dirs: " + (matlabCandidates.isEmpty() ? "" : matlabCandidates) - } -} \ No newline at end of file diff --git a/app/src/main/java/PolyphenyConnection.java b/app/src/main/java/PolyphenyConnection.java index 14206ab..486f43b 100644 --- a/app/src/main/java/PolyphenyConnection.java +++ b/app/src/main/java/PolyphenyConnection.java @@ -3,171 +3,188 @@ import java.net.Socket; public class PolyphenyConnection { + private Connection connection; private final String url, username, password; - /* - @Description - - Constructor supporting lazy-open: Stores logins; connects on first use to protect server resources - - @params: - - url: the url of the database - - username: username to access the database with - - password: password to the corresponding username - - @return - - Creates a PolyphenyConnection object that can be passed to the ExecuteQuery class and used to run queries - */ - public PolyphenyConnection(String url, String username, String password) throws Exception { - this.url = url; - this.username = username; - this.password = password; - this.connection = null; //The connection is established later on when needed. Lazy open prevents accidental resource leaks induced by user - StartLocalPolypheny(); //Starts Polypheny locally on the machine if it's not already running + + /** + * @Description - Constructor supporting lazy-open: Stores logins; connects on + * first use to + * protect server resources + * + * @params: - url: the url of the database - username: username to access the + * database with - + * password: password to the corresponding username + * + * @return - Creates a PolyphenyConnection object that can be passed to the + * ExecuteQuery class + * and used to run queries + **/ + public PolyphenyConnection( String url, String username, String password ) throws Exception { + this.url = url; + this.username = username; + this.password = password; + this.connection = null; // The connection is established later on when needed. Lazy open + // prevents + // accidental resource leaks induced by user + StartLocalPolypheny(); // Starts Polypheny locally on the machine if it's not already + // running waitForPolyphenyReady(); } - /* - @Description - - Checks if Polypheny is running locally and starts it if not - - @param - - @return - - Boolean true or Exception: because Polypheny is either running in the end or was started. - */ + /** + * @Description - Checks if Polypheny is running locally and starts it if not + * + * @param - + * @return - Boolean true or Exception: because Polypheny is either running in + * the end or was + * started. + **/ private boolean isPolyphenyRunning() { - try (Socket socket = new Socket("localhost", 20590)) { + try ( Socket socket = new Socket( "localhost", 20590 ) ) { return true; // Able to connect -> running, Otherwise an Exception will be thrown later - } catch (Exception e) { + } catch ( Exception e ) { return false; } } + private void waitForPolyphenyReady() { int timeoutTime = 30; int timeWaited = 0; - while (timeWaited < timeoutTime) { - try (Socket socket = new Socket("127.0.0.1", 20590)) { - System.out.println("Polypheny is ready."); + while ( timeWaited < timeoutTime ) { + try ( Socket socket = new Socket( "127.0.0.1", 20590 ) ) { + System.out.println( "Polypheny is ready." ); return; - } catch (IOException e) { - System.out.println(" Waiting for Polypheny to become ready..."); + } catch ( IOException e ) { + System.out.println( " Waiting for Polypheny to become ready..." ); try { - Thread.sleep(1000); - } catch (InterruptedException ie) { + Thread.sleep( 1000 ); + } catch ( InterruptedException ie ) { Thread.currentThread().interrupt(); - throw new RuntimeException("Interrupted while waiting for Polypheny."); + throw new RuntimeException( "Interrupted while waiting for Polypheny." ); } timeWaited++; } } - throw new RuntimeException("Polypheny did not start within timeout."); + throw new RuntimeException( "Polypheny did not start within timeout." ); } - /* - @REQUIREMENTS - - polypheny.jar in lib folder - - @Description - - Starts the Polypheny application on the local machine - - @param - - @return - - */ + /** + * @REQUIREMENTS - polypheny.jar in lib folder + * + * @Description - Starts the Polypheny application on the local machine + * + * @param - + * @return - + **/ public void StartLocalPolypheny() { try { - if (!isPolyphenyRunning()) { - System.out.println("Polypheny not running. Attempting to start Polypheny application"); - ProcessBuilder pb = new ProcessBuilder("java", "-jar", "libs/polypheny.jar"); + if ( !isPolyphenyRunning() ) { + System.out.println( + "Polypheny not running. Attempting to start Polypheny application" ); + ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "libs/polypheny.jar" ); pb.inheritIO(); pb.start(); // Wait until JDBC is ready - while (!isJdbcAvailable("jdbc:polypheny://localhost:20590", "pa", "")) { - System.out.println("Waiting for JDBC to become available..."); - Thread.sleep(1000); + while ( !isJdbcAvailable( "jdbc:polypheny://localhost:20590", "pa", "" ) ) { + System.out.println( "Waiting for JDBC to become available..." ); + Thread.sleep( 1000 ); } - System.out.println("Polypheny JDBC is ready."); + System.out.println( "Polypheny JDBC is ready." ); } else { - System.out.println("Polypheny is already running."); + System.out.println( "Polypheny is already running." ); } - } catch (Exception e) { - System.err.println("Could not start Polypheny: " + e.getMessage()); + } catch ( Exception e ) { + System.err.println( "Could not start Polypheny: " + e.getMessage() ); } } - private boolean isJdbcAvailable(String jdbcUrl, String user, String pass) { - try (java.sql.Connection conn = java.sql.DriverManager.getConnection(jdbcUrl, user, pass)) { + + private boolean isJdbcAvailable( String jdbcUrl, String user, String pass ) { + try ( java.sql.Connection conn = java.sql.DriverManager.getConnection( jdbcUrl, user, pass ) ) { return conn != null && !conn.isClosed(); - } catch (Exception e) { + } catch ( Exception e ) { return false; } } - /* - @Description - - Opens the server connection to Polypheny if needed (reuse otherwise). Checking the if-clause in java is a lot faster, than iterative - opening and closing of the connection after every use for large numbers of queries, as it eliminates the ~10ms matlab-java crossover - that opening and closing a connection from matlab would create. For 1M queries that avoids 1M * 10ms = ~10 000 sec = 2.8 hrs of overhead. - @param - - @return - - */ + /** + * @Description - Opens the server connection to Polypheny if needed (reuse + * otherwise). Checking + * the if-clause in java is a lot faster, than iterative opening + * and closing of the + * connection after every use for large numbers of queries, as it + * eliminates the + * ~10ms matlab-java crossover that opening and closing a + * connection from matlab + * would create. For 1M queries that avoids 1M * 10ms = ~10 000 sec + * = 2.8 hrs of + * overhead. + * + * @param - + * @return - + **/ public void openIfNeeded() { - if (connection == null) { + if ( connection == null ) { try { - Class.forName("org.polypheny.jdbc.PolyphenyDriver"); // load driver - connection = DriverManager.getConnection(url, username, password); //establish connection - } catch (Exception e) { - throw new RuntimeException("Failed to open connection", e); + Class.forName( "org.polypheny.jdbc.PolyphenyDriver" ); // load driver + connection = DriverManager.getConnection( url, username, password ); // establish + // connection + } catch ( Exception e ) { + throw new RuntimeException( "Failed to open connection", e ); } } } - /* - @Description - - Closes connection if open - @param - - @return - - */ + /** + * @Description - Closes connection if open + * + * @param - + * @return - + **/ public void close() { try { - if (connection != null && !connection.isClosed()) { + if ( connection != null && !connection.isClosed() ) { connection.close(); } - } catch (SQLException e) { - System.err.println("Failed to close connection: " + e.getMessage()); + } catch ( SQLException e ) { + System.err.println( "Failed to close connection: " + e.getMessage() ); } } - /* - @Description - - Getter function for the connection variable of PolyphenyConnection - - @param - - @return Connection connection variable of the PolyphenyConnection class - */ + + /** + * @Description - Getter function for the connection variable of + * PolyphenyConnection + * + * @param - + * @return Connection connection variable of the PolyphenyConnection class + **/ public Connection get_connection() { return this.connection; } - /* - @Description - - Setter function for the connection variable - @param input_connection: The connection we want to set your PolyphenyConnection object to. - @return - - */ - public void set_connection(Connection input_connection){ + /** + * @Description - Setter function for the connection variable + * + * @param input_connection: The connection we want to set your + * PolyphenyConnection object to. + * @return - + **/ + public void set_connection( Connection input_connection ) { this.connection = input_connection; } - } diff --git a/app/src/main/java/QueryExecutor.java b/app/src/main/java/QueryExecutor.java index a01defb..5d20fa6 100644 --- a/app/src/main/java/QueryExecutor.java +++ b/app/src/main/java/QueryExecutor.java @@ -2,68 +2,75 @@ import java.util.ArrayList; import java.util.List; -public class QueryExecutor{ +public class QueryExecutor { + private PolyphenyConnection polyconnection; - - /* - @Description - - Constructor - - @param - - polyconnection: PolyphenyConnection object that holds the connection details to the Database. It's used to execute queries - @return - - Object of the query (SQL: empty, scalar or table; MongoQL: TODO; Cypher: TODO) - */ - public QueryExecutor(PolyphenyConnection polyconnection) { + + + /** + * @Description + * - Constructor + * + * @param + * - polyconnection: PolyphenyConnection object that holds the connection + * details to the Database. It's used to execute queries + * @return + * - Object of the query (SQL: empty, scalar or table; MongoQL: TODO; + * Cypher: TODO) + **/ + public QueryExecutor( PolyphenyConnection polyconnection ) { this.polyconnection = polyconnection; } - /* - @Description - - Executes the query depending on the language given by the user - - @param - - language: The database language that is used (e.g. SQL, MongoQL, Cypher) - - namespace: Name of the database namespace (e.g. emps, students) - - query: The query-text to be executed (e.g. FROM emps SELECT *) - @return - - ResultToMatlab(rs) which is a Matlab compatible object that is cast to the Matlab user. - */ - public Object execute(String language, String query) { + /** + * @Description + * - Executes the query depending on the language given by the user + * + * @param + * - language: The database language that is used (e.g. SQL, MongoQL, + * Cypher) + * - namespace: Name of the database namespace (e.g. emps, students) + * - query: The query-text to be executed (e.g. FROM emps SELECT *) + * @return + * - ResultToMatlab(rs) which is a Matlab compatible object that is cast + * to the Matlab user. + **/ + public Object execute( String language, String query ) { polyconnection.openIfNeeded(); - switch (language.toLowerCase()) { + switch ( language.toLowerCase() ) { default: - System.err.println("Unsupported language: " + language); + System.err.println( "Unsupported language: " + language ); return null; case "sql": - try (Statement stmt = polyconnection.get_connection().createStatement(); - ResultSet rs = stmt.executeQuery(query)) - { - return ResultToMatlab(rs); - } catch (Exception e) { - System.err.println("SQL execution failed: " + e.getMessage()); + try ( Statement stmt = polyconnection.get_connection().createStatement(); ResultSet rs = stmt.executeQuery( query ) ) { + return ResultToMatlab( rs ); + } catch ( Exception e ) { + System.err.println( "SQL execution failed: " + e.getMessage() ); return null; } case "mongoql": - throw new UnsupportedOperationException("MongoQL execution not yet implemented."); + throw new UnsupportedOperationException( "MongoQL execution not yet implemented." ); case "cypher": - throw new UnsupportedOperationException("Cypher execution not yet implemented."); + throw new UnsupportedOperationException( "Cypher execution not yet implemented." ); } } - /* - @Description - - Casts the result of the queries to MatlabObjects, depending on the Database language (SQL, MongoQL, Cypher) - @param ResultSet rs: The result object of the query - @return engine.getVariable("MatlabTable") which is either null/scalar/table (SQL), document (MongoQL) or TODO (Cypher) - */ - public Object ResultToMatlab(ResultSet rs) throws Exception { + /** + * @Description + * - Casts the result of the queries to MatlabObjects, depending on + * the Database language (SQL, MongoQL, Cypher) + * + * @param ResultSet rs: The result object of the query + * @return engine.getVariable("MatlabTable") which is either null/scalar/table + * (SQL), document (MongoQL) or TODO (Cypher) + **/ + public Object ResultToMatlab( ResultSet rs ) throws Exception { ResultSetMetaData meta = rs.getMetaData(); int colCount = meta.getColumnCount(); @@ -73,8 +80,8 @@ public Object ResultToMatlab(ResultSet rs) throws Exception { // ───────────────────────────── // Case 1: Empty Result // ───────────────────────────── - if (!rs.next()) { - System.out.println("Empty result set."); + if ( !rs.next() ) { + System.out.println( "Empty result set." ); Result = null; return Result; } @@ -82,39 +89,38 @@ public Object ResultToMatlab(ResultSet rs) throws Exception { // ───────────────────────────── // Case 2: Scalar Result // ───────────────────────────── - if (colCount == 1 && rs.isLast()) { - System.out.println("Scalar result set."); - Object scalar = rs.getObject(1); + if ( colCount == 1 && rs.isLast() ) { + System.out.println( "Scalar result set." ); + Object scalar = rs.getObject( 1 ); return scalar; } // ───────────────────────────── // Case 3: Tabular Result (≥1 column, ≥1 row) // ───────────────────────────── - String[] colNames = new String[colCount]; //get the column names to name the columns later - - for (int i = 1; i <= colCount; i++) { - colNames[i - 1] = meta.getColumnName(i); //assign the column names to the array + String[] colNames = new String[colCount]; // get the column names to name the columns later + + for ( int i = 1; i <= colCount; i++ ) { + colNames[i - 1] = meta.getColumnName( i ); // assign the column names to the array } - List rows = new ArrayList<>(); // List of arrays to store the rows returned by the query do { Object[] row = new Object[colCount]; // Creates new array that will store the queries entries - for (int i=0; i Date: Wed, 27 Aug 2025 09:02:38 +0200 Subject: [PATCH 13/44] Cleaned up documentation that was broken due to the format change. --- app/src/main/java/PolyphenyConnection.java | 75 ++++++++++------------ app/src/main/java/QueryExecutor.java | 23 +++---- 2 files changed, 43 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/PolyphenyConnection.java b/app/src/main/java/PolyphenyConnection.java index 486f43b..2794d2f 100644 --- a/app/src/main/java/PolyphenyConnection.java +++ b/app/src/main/java/PolyphenyConnection.java @@ -9,17 +9,16 @@ public class PolyphenyConnection { /** - * @Description - Constructor supporting lazy-open: Stores logins; connects on - * first use to - * protect server resources + * @Description + * - Constructor supporting lazy-open: Stores logins; connects on first use to protect server resources. * - * @params: - url: the url of the database - username: username to access the - * database with - - * password: password to the corresponding username + * @param url: the url of the database + * @param username: username to access the database with + * @param password: password to the corresponding username * - * @return - Creates a PolyphenyConnection object that can be passed to the - * ExecuteQuery class - * and used to run queries + * @return + * - Creates a PolyphenyConnection object that can be passed to the ExecuteQuery class and used to + * run queries **/ public PolyphenyConnection( String url, String username, String password ) throws Exception { this.url = url; @@ -35,12 +34,11 @@ public PolyphenyConnection( String url, String username, String password ) throw /** - * @Description - Checks if Polypheny is running locally and starts it if not + * @Description + * - Checks if Polypheny is running locally and starts it if not * - * @param - - * @return - Boolean true or Exception: because Polypheny is either running in - * the end or was - * started. + * @return + * - Boolean true or Exception: because Polypheny is either running in the end or was started. **/ private boolean isPolyphenyRunning() { @@ -77,12 +75,12 @@ private void waitForPolyphenyReady() { /** - * @REQUIREMENTS - polypheny.jar in lib folder + * @REQUIREMENTS + * - polypheny.jar in lib folder * - * @Description - Starts the Polypheny application on the local machine + * @Description + * - Starts the Polypheny application on the local machine * - * @param - - * @return - **/ public void StartLocalPolypheny() { try { @@ -119,20 +117,15 @@ private boolean isJdbcAvailable( String jdbcUrl, String user, String pass ) { /** - * @Description - Opens the server connection to Polypheny if needed (reuse - * otherwise). Checking - * the if-clause in java is a lot faster, than iterative opening - * and closing of the - * connection after every use for large numbers of queries, as it - * eliminates the - * ~10ms matlab-java crossover that opening and closing a - * connection from matlab - * would create. For 1M queries that avoids 1M * 10ms = ~10 000 sec - * = 2.8 hrs of - * overhead. + * @Description + * - Opens the server connection to Polypheny if needed (reuse otherwise). Checking the + * if-clause in java is a lot faster, than iterative opening and closing of the connection after every + * use for large numbers of queries, as it eliminates the ~10ms matlab-java crossover that opening and + * closing a connection from matlab would create. For 1M queries that avoids 1M*10ms = ~10 000sec=2.8 hrs + * of overhead. * - * @param - - * @return - + * @param + * @return **/ public void openIfNeeded() { if ( connection == null ) { @@ -148,10 +141,9 @@ public void openIfNeeded() { /** - * @Description - Closes connection if open + * @Description + * - Closes connection if open * - * @param - - * @return - **/ public void close() { try { @@ -165,11 +157,11 @@ public void close() { /** - * @Description - Getter function for the connection variable of - * PolyphenyConnection + * @Description + * - Getter function for the connection variable of PolyphenyConnection * - * @param - - * @return Connection connection variable of the PolyphenyConnection class + * @return + * - Connection connection variable of the PolyphenyConnection class **/ public Connection get_connection() { return this.connection; @@ -177,11 +169,10 @@ public Connection get_connection() { /** - * @Description - Setter function for the connection variable + * @Description + * - Setter function for the connection variable * - * @param input_connection: The connection we want to set your - * PolyphenyConnection object to. - * @return - + * @param input_connection: The connection we want to set your PolyphenyConnection object to. **/ public void set_connection( Connection input_connection ) { this.connection = input_connection; diff --git a/app/src/main/java/QueryExecutor.java b/app/src/main/java/QueryExecutor.java index 5d20fa6..3e9edb6 100644 --- a/app/src/main/java/QueryExecutor.java +++ b/app/src/main/java/QueryExecutor.java @@ -11,11 +11,9 @@ public class QueryExecutor { * @Description * - Constructor * - * @param - * - polyconnection: PolyphenyConnection object that holds the connection + * @param polyconnection: PolyphenyConnection object that holds the connection * details to the Database. It's used to execute queries - * @return - * - Object of the query (SQL: empty, scalar or table; MongoQL: TODO; + * @return: Object of the query (SQL: empty, scalar or table; MongoQL: TODO; * Cypher: TODO) **/ public QueryExecutor( PolyphenyConnection polyconnection ) { @@ -27,13 +25,11 @@ public QueryExecutor( PolyphenyConnection polyconnection ) { * @Description * - Executes the query depending on the language given by the user * - * @param - * - language: The database language that is used (e.g. SQL, MongoQL, + * @param language: The database language that is used (e.g. SQL, MongoQL, * Cypher) - * - namespace: Name of the database namespace (e.g. emps, students) - * - query: The query-text to be executed (e.g. FROM emps SELECT *) - * @return - * - ResultToMatlab(rs) which is a Matlab compatible object that is cast + * @param query: The query-text to be executed (e.g. FROM emps SELECT *) + * + * @return: ResultToMatlab(rs) which is a Matlab compatible object that is cast * to the Matlab user. **/ public Object execute( String language, String query ) { @@ -66,9 +62,10 @@ public Object execute( String language, String query ) { * - Casts the result of the queries to MatlabObjects, depending on * the Database language (SQL, MongoQL, Cypher) * - * @param ResultSet rs: The result object of the query - * @return engine.getVariable("MatlabTable") which is either null/scalar/table - * (SQL), document (MongoQL) or TODO (Cypher) + * @param rs: The result object of the query of type ResultSet + * + * @return: Result from the query which is either null/scalar/table (SQL), document (MongoQL) + * or TODO (Cypher) **/ public Object ResultToMatlab( ResultSet rs ) throws Exception { From 1798f473cdc82bfe90d4f27554238ed6716d73ab Mon Sep 17 00:00:00 2001 From: fygo97 Date: Thu, 11 Sep 2025 20:49:18 +0200 Subject: [PATCH 14/44] Ignore large JAR --- .gitignore | Bin 279 -> 379 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.gitignore b/.gitignore index f569ef030ecc5b9022ec36acbe03d813964c2073..ed3d87c069136711d4156021bf36d59d7ef0a306 100644 GIT binary patch delta 216 zcmbQv^qXmdm?AHimR@pBVsSBuk&~a!%avbRqR-2fRGOKS0;G8-8mbupDK2FN|6twX zg4E>9w9I6Mw9K4TpcXxs;P~K@{GwD|u8@q<+@xZ?lq8UzMm26;uAI!IVtp+TcfBDG zP;GiqVoDBBCGW(S>XA?zfT|cW8Il-^8T1(n81fl%7%G8u216=C9+0KSkj0S5P{hE? Kzy*{cP8|RkN;CTa delta 100 zcmey(G@WUJm^7D`UUE)iaWRn2$xr9XFD=pMN-E9FNzvz;Xs*V{H8DX%luOSgI6k-} zzbKU}B%?GpsaP*1iECo78V6TSW>T@f7T3gmhMZh_=|zbtIjLL|f2*@YjNk$Q@WmdS From e78f151e7347926d9168cfcf2fbfb2e155dd44d7 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Thu, 11 Sep 2025 22:00:17 +0200 Subject: [PATCH 15/44] 1. Added host and port as inputs to PolyphenyConnection. Now the user determines the connections host and port. 2. Refactored to the Project-own coding conventions. 3. Added Commit and Rollback logic to PolyphenyConnection. 4. Removed the autostart logic from PolyphenyConnection as they should go to the JUnitTests. Adapted the res tof the code accordingly. 5. Added Documentation for the code. 6. Adapted QueryExecutor so only executeQuery takes the lanugage, not the constructor --- .gitignore | Bin 379 -> 416 bytes .vscode/launch.json | 2 +- .vscode/settings.json | 5 +- app/bin/main/MatlabTest.m | 8 + app/bin/main/Polypheny.m | 56 ++++++ app/bin/main/startup.m | 17 ++ app/build.gradle | 17 +- app/src/main/java/Main.java | 17 +- app/src/main/java/MatlabTest.m | 8 + app/src/main/java/Polypheny.m | 56 ++++++ app/src/main/java/PolyphenyConnection.java | 181 ------------------ app/src/main/java/QuickTest.java | 45 +++-- app/src/main/java/execeuteQuery.m | 1 - .../PolyphenyConnection.java | 155 +++++++++++++++ .../QueryExecutor.java | 2 + app/src/main/java/startup.m | 17 ++ 16 files changed, 371 insertions(+), 216 deletions(-) create mode 100644 app/bin/main/MatlabTest.m create mode 100644 app/bin/main/Polypheny.m create mode 100644 app/bin/main/startup.m create mode 100644 app/src/main/java/MatlabTest.m create mode 100644 app/src/main/java/Polypheny.m delete mode 100644 app/src/main/java/PolyphenyConnection.java delete mode 100644 app/src/main/java/execeuteQuery.m create mode 100644 app/src/main/java/polyphenyconnector/PolyphenyConnection.java rename app/src/main/java/{ => polyphenyconnector}/QueryExecutor.java (99%) create mode 100644 app/src/main/java/startup.m diff --git a/.gitignore b/.gitignore index ed3d87c069136711d4156021bf36d59d7ef0a306..45aca94904e663cdbd2308b1b5d95588cf92c6aa 100644 GIT binary patch delta 238 zcmZ9GI|>3Z5Qb43ola02n}DDoY!)jo;0?4Cbh3%ND`T?cQCN8b!Q8-;crhE}qq3SA ze&+lBXZzl{jm9wN1)+0kk}1IipMwZB$sFQx+Jd+r41urKyR|n~!KMl);kXnKuhTA2 zEnGf__*nFSmlh={h|k&OivXCgy42~9`AV_I(7CCUK4(b8k7yZc$*^fY`=KV~AYI#! qe)~rM0Z!-%1w$z_quIfbRF*k!L*YZ}_KmZo>Hj>1(OMO$IJ^M&4N#~6 delta 216 zcmZ3${F`Zlm?AHimR@pBVsSBuk&~a!%avbRqR-2fRGOKS0;G8-8mbupDK2FN|6twX zg4E>9w9I6Mw9K4TpcXxs;P~K@{GwD|u8@q<+@xZ?lq8UzMm26;uAI!IVtp+TcfBDG zP;GiqVoDBBCGW(S>XA?zfT|cW8Il-^8T1(n81fl%7%G8u216=C9+0KSkj0S5P{hE? Kzy*{cP8|Rsxikd; diff --git a/.vscode/launch.json b/.vscode/launch.json index 7b5cae5..3865e57 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "name": "Main", "request": "launch", "mainClass": "Main", - "projectName": "Learning Contract_7fe425cb" + "projectName": "Matlab_Connector" }, { "type": "java", diff --git a/.vscode/settings.json b/.vscode/settings.json index df6cbc8..afa78c2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,11 +4,14 @@ ], "java.project.referencedLibraries": [], "cSpell.words": [ + "empid", "emps", "JDBC", + "johnrengelman", "mongoql", "polyconnection", - "Polypheny" + "Polypheny", + "polyphenyconnector" ], "java.format.enabled": true, "java.format.settings.url": ".vscode/Polypheny-Style.xml", diff --git a/app/bin/main/MatlabTest.m b/app/bin/main/MatlabTest.m new file mode 100644 index 0000000..8470642 --- /dev/null +++ b/app/bin/main/MatlabTest.m @@ -0,0 +1,8 @@ +startup(); + +p = polypheny.Polypheny('sql', 'jdbc:polypheny://localhost:20590', 'pa', ''); + +T = p.query("SELECT 1 AS test_column"); +disp(T); + +p.close(); diff --git a/app/bin/main/Polypheny.m b/app/bin/main/Polypheny.m new file mode 100644 index 0000000..6c6cc55 --- /dev/null +++ b/app/bin/main/Polypheny.m @@ -0,0 +1,56 @@ +classdef Polypheny < handle +% POLYPHENY MATLAB wrapper for the Polypheny Java connector. Wraps polyphenyconnector.PolyphenyConnection +% and QueryExecutor to run queries from MATLAB + properties (Access = private) + language % 'sql' | 'mongoql' | 'cypher' + polyConnection % Java PolyphenyConnection + queryExecutor % Java QueryExecutor + + end + + methods + + % + function PolyWrapper = Polypheny(language, url, user, password ) + % Polypheny( LANGUAGE, URL, USER, PASSWORD ): Set up Java connection + executor + % LANGUAGE: The database language used for the query. Must be sql, mongoql or cypher. + % URL: The database url + % USER: The username + % PASSWORD: The password + % Polywrapper: A Matlab object that is a wrapper for the two java classes + % QueryExecutioner.java and PolyphenyConnection.java + PolyWrapper.language = language; + PolyWrapper.polyConnection = javaObject( "polyphenyconnector.PolyphenyConnection", url, user, password ); + PolyWrapper.queryExecutor = javaObject( "polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + end + + + + function T = query( PolyWrapper, queryStr ) + % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java + % POLYWRAPPER: The PolyWrapper Matlab object + % QUERYSTR: The queryStr set by the user + % @return T: The result of the query -> return type differs for SQL,MongoQl and Cyper + r = PolyWrapper.queryExecutor.execute(string(PolyWrapper.language), queryStr ); + + if isempty( r ) + T = []; + + elseif isscalar( r ) + T = r; + + else + % Expect Java side to return { colNames, data } + colNames = cell( r(1) ); + data = r(2); + T = cell2table( data, 'VariableNames', colNames ); + end + end + + function close( PolyWrapper ) + % close( POLYWRAPPER ): Close the Java connection + % POLYWRAPPER: The PolyWrapper Matlab object + PolyWrapper.polyConnection.close(); + end + end +end diff --git a/app/bin/main/startup.m b/app/bin/main/startup.m new file mode 100644 index 0000000..5d1850d --- /dev/null +++ b/app/bin/main/startup.m @@ -0,0 +1,17 @@ +function startup + % Get root folder of the toolbox + root = fileparts( mfilename( 'fullpath' ) ); + + % Add Java JARs + javaaddpath( fullfile( root, 'jar', 'polypheny-all.jar' ) ); + javaaddpath( fullfile( root, 'libs', 'polypheny-jdbc-driver-2.3.jar' ) ); + + % Add MATLAB class folder to path + % addpath( fullfile( root, '+polypheny' ) ); % optional, allows tab-completion & easier access + + % Set Polypheny server jar path (for ProcessBuilder) + javaMethod( 'setProperty', 'java.lang.System', ... + 'polypheny.jar', fullfile( root, 'libs', 'polypheny.jar' ) ); + + disp('Polypheny connector initialized.'); +end diff --git a/app/build.gradle b/app/build.gradle index 0da44a2..0521ea3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,19 +8,20 @@ application { mainClass = 'QuickTest' } // currently running QuickTest.java as a repositories { mavenCentral() } dependencies { - // Pull all JARs from the **root** libs/ folder ( - implementation fileTree(dir: "${rootProject.projectDir}/libs", include: ["*.jar"]) - - // Silences SLF4J warnings + // SLF4J implementation to silence warnings (bundled into the shaded JAR) implementation 'org.slf4j:slf4j-simple:2.0.16' + + // JDBC driver: keep separate in toolbox/libs/, not bundled into the shaded JAR + compileOnly files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) + runtimeOnly files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) } shadowJar { - archiveBaseName.set('polypheny') - archiveClassifier.set('all') - archiveVersion.set('') // => app/build/libs/polypheny-all.jar + archiveBaseName.set('polypheny') // final jar name base + archiveClassifier.set('all') // adds -all suffix + archiveVersion.set('') // => app/build/libs/polypheny-all.jar } java { - toolchain { languageVersion = JavaLanguageVersion.of(17) } + toolchain { languageVersion = JavaLanguageVersion.of(8) } } diff --git a/app/src/main/java/Main.java b/app/src/main/java/Main.java index a2697d8..11a422f 100644 --- a/app/src/main/java/Main.java +++ b/app/src/main/java/Main.java @@ -1,16 +1,23 @@ +import polyphenyconnector.PolyphenyConnection; +import polyphenyconnector.QueryExecutor; + public class Main { - public static void main(String[] args) { + + public static void main( String[] args ) { try { + String host = "localhost"; + int port = "public"; String url = "jdbc:polypheny://localhost/public"; String user = "pa"; String pass = ""; - PolyphenyConnection conn = new PolyphenyConnection(url, user, pass); - QueryExecutor executor = new QueryExecutor(conn); - executor.execute("sql", "SELECT * FROM emps;"); + PolyphenyConnection conn = new PolyphenyConnection( host, port, user, pass ); + QueryExecutor executor = new QueryExecutor( conn ); + executor.execute( "sql", "SELECT * FROM emps;" ); conn.close(); - } catch (Exception e) { + } catch ( Exception e ) { e.printStackTrace(); } } + } diff --git a/app/src/main/java/MatlabTest.m b/app/src/main/java/MatlabTest.m new file mode 100644 index 0000000..8470642 --- /dev/null +++ b/app/src/main/java/MatlabTest.m @@ -0,0 +1,8 @@ +startup(); + +p = polypheny.Polypheny('sql', 'jdbc:polypheny://localhost:20590', 'pa', ''); + +T = p.query("SELECT 1 AS test_column"); +disp(T); + +p.close(); diff --git a/app/src/main/java/Polypheny.m b/app/src/main/java/Polypheny.m new file mode 100644 index 0000000..6c6cc55 --- /dev/null +++ b/app/src/main/java/Polypheny.m @@ -0,0 +1,56 @@ +classdef Polypheny < handle +% POLYPHENY MATLAB wrapper for the Polypheny Java connector. Wraps polyphenyconnector.PolyphenyConnection +% and QueryExecutor to run queries from MATLAB + properties (Access = private) + language % 'sql' | 'mongoql' | 'cypher' + polyConnection % Java PolyphenyConnection + queryExecutor % Java QueryExecutor + + end + + methods + + % + function PolyWrapper = Polypheny(language, url, user, password ) + % Polypheny( LANGUAGE, URL, USER, PASSWORD ): Set up Java connection + executor + % LANGUAGE: The database language used for the query. Must be sql, mongoql or cypher. + % URL: The database url + % USER: The username + % PASSWORD: The password + % Polywrapper: A Matlab object that is a wrapper for the two java classes + % QueryExecutioner.java and PolyphenyConnection.java + PolyWrapper.language = language; + PolyWrapper.polyConnection = javaObject( "polyphenyconnector.PolyphenyConnection", url, user, password ); + PolyWrapper.queryExecutor = javaObject( "polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + end + + + + function T = query( PolyWrapper, queryStr ) + % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java + % POLYWRAPPER: The PolyWrapper Matlab object + % QUERYSTR: The queryStr set by the user + % @return T: The result of the query -> return type differs for SQL,MongoQl and Cyper + r = PolyWrapper.queryExecutor.execute(string(PolyWrapper.language), queryStr ); + + if isempty( r ) + T = []; + + elseif isscalar( r ) + T = r; + + else + % Expect Java side to return { colNames, data } + colNames = cell( r(1) ); + data = r(2); + T = cell2table( data, 'VariableNames', colNames ); + end + end + + function close( PolyWrapper ) + % close( POLYWRAPPER ): Close the Java connection + % POLYWRAPPER: The PolyWrapper Matlab object + PolyWrapper.polyConnection.close(); + end + end +end diff --git a/app/src/main/java/PolyphenyConnection.java b/app/src/main/java/PolyphenyConnection.java deleted file mode 100644 index 2794d2f..0000000 --- a/app/src/main/java/PolyphenyConnection.java +++ /dev/null @@ -1,181 +0,0 @@ -import java.sql.*; -import java.io.IOException; -import java.net.Socket; - -public class PolyphenyConnection { - - private Connection connection; - private final String url, username, password; - - - /** - * @Description - * - Constructor supporting lazy-open: Stores logins; connects on first use to protect server resources. - * - * @param url: the url of the database - * @param username: username to access the database with - * @param password: password to the corresponding username - * - * @return - * - Creates a PolyphenyConnection object that can be passed to the ExecuteQuery class and used to - * run queries - **/ - public PolyphenyConnection( String url, String username, String password ) throws Exception { - this.url = url; - this.username = username; - this.password = password; - this.connection = null; // The connection is established later on when needed. Lazy open - // prevents - // accidental resource leaks induced by user - StartLocalPolypheny(); // Starts Polypheny locally on the machine if it's not already - // running - waitForPolyphenyReady(); - } - - - /** - * @Description - * - Checks if Polypheny is running locally and starts it if not - * - * @return - * - Boolean true or Exception: because Polypheny is either running in the end or was started. - **/ - - private boolean isPolyphenyRunning() { - try ( Socket socket = new Socket( "localhost", 20590 ) ) { - return true; // Able to connect -> running, Otherwise an Exception will be thrown later - } catch ( Exception e ) { - return false; - } - } - - - private void waitForPolyphenyReady() { - int timeoutTime = 30; - int timeWaited = 0; - - while ( timeWaited < timeoutTime ) { - try ( Socket socket = new Socket( "127.0.0.1", 20590 ) ) { - System.out.println( "Polypheny is ready." ); - return; - } catch ( IOException e ) { - System.out.println( " Waiting for Polypheny to become ready..." ); - try { - Thread.sleep( 1000 ); - } catch ( InterruptedException ie ) { - Thread.currentThread().interrupt(); - throw new RuntimeException( "Interrupted while waiting for Polypheny." ); - } - timeWaited++; - } - } - - throw new RuntimeException( "Polypheny did not start within timeout." ); - } - - - /** - * @REQUIREMENTS - * - polypheny.jar in lib folder - * - * @Description - * - Starts the Polypheny application on the local machine - * - **/ - public void StartLocalPolypheny() { - try { - if ( !isPolyphenyRunning() ) { - System.out.println( - "Polypheny not running. Attempting to start Polypheny application" ); - ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "libs/polypheny.jar" ); - pb.inheritIO(); - pb.start(); - - // Wait until JDBC is ready - while ( !isJdbcAvailable( "jdbc:polypheny://localhost:20590", "pa", "" ) ) { - System.out.println( "Waiting for JDBC to become available..." ); - Thread.sleep( 1000 ); - } - - System.out.println( "Polypheny JDBC is ready." ); - } else { - System.out.println( "Polypheny is already running." ); - } - } catch ( Exception e ) { - System.err.println( "Could not start Polypheny: " + e.getMessage() ); - } - } - - - private boolean isJdbcAvailable( String jdbcUrl, String user, String pass ) { - try ( java.sql.Connection conn = java.sql.DriverManager.getConnection( jdbcUrl, user, pass ) ) { - return conn != null && !conn.isClosed(); - } catch ( Exception e ) { - return false; - } - } - - - /** - * @Description - * - Opens the server connection to Polypheny if needed (reuse otherwise). Checking the - * if-clause in java is a lot faster, than iterative opening and closing of the connection after every - * use for large numbers of queries, as it eliminates the ~10ms matlab-java crossover that opening and - * closing a connection from matlab would create. For 1M queries that avoids 1M*10ms = ~10 000sec=2.8 hrs - * of overhead. - * - * @param - * @return - **/ - public void openIfNeeded() { - if ( connection == null ) { - try { - Class.forName( "org.polypheny.jdbc.PolyphenyDriver" ); // load driver - connection = DriverManager.getConnection( url, username, password ); // establish - // connection - } catch ( Exception e ) { - throw new RuntimeException( "Failed to open connection", e ); - } - } - } - - - /** - * @Description - * - Closes connection if open - * - **/ - public void close() { - try { - if ( connection != null && !connection.isClosed() ) { - connection.close(); - } - } catch ( SQLException e ) { - System.err.println( "Failed to close connection: " + e.getMessage() ); - } - } - - - /** - * @Description - * - Getter function for the connection variable of PolyphenyConnection - * - * @return - * - Connection connection variable of the PolyphenyConnection class - **/ - public Connection get_connection() { - return this.connection; - } - - - /** - * @Description - * - Setter function for the connection variable - * - * @param input_connection: The connection we want to set your PolyphenyConnection object to. - **/ - public void set_connection( Connection input_connection ) { - this.connection = input_connection; - } - -} diff --git a/app/src/main/java/QuickTest.java b/app/src/main/java/QuickTest.java index 78111e2..df7fad6 100644 --- a/app/src/main/java/QuickTest.java +++ b/app/src/main/java/QuickTest.java @@ -1,45 +1,52 @@ import java.util.Arrays; +import polyphenyconnector.PolyphenyConnection; +import polyphenyconnector.QueryExecutor; + public class QuickTest { - public static void main(String[] args) throws Exception { - PolyphenyConnection conn = new PolyphenyConnection("jdbc:polypheny://localhost:20590", "pa", ""); + + public static void main( String[] args ) throws Exception { + PolyphenyConnection conn = new PolyphenyConnection( "jdbc:polypheny://localhost:20590", "pa", "" ); try { - QueryExecutor exec = new QueryExecutor(conn); + QueryExecutor exec = new QueryExecutor( conn ); // 1) Scalar smoke test - Object r1 = exec.execute("sql", "SELECT 1 AS x"); - System.out.println("Scalar result: " + r1); + Object r1 = exec.execute( "sql", "SELECT 1 AS x" ); + System.out.println( "Scalar result: " + r1 ); // 2) Table smoke test - Object r2 = exec.execute("sql", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4"); - printTable(r2); + Object r2 = exec.execute( "sql", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); + printTable( r2 ); + //TODO: change the empid test to something thats independent of that. // 3) First row from emps (1-row table) - Object r3 = exec.execute("sql", "SELECT * FROM emps LIMIT 1"); - System.out.println("First row from emps:"); - printTable(r3); + Object r3 = exec.execute( "sql", "SELECT * FROM emps LIMIT 1" ); + System.out.println( "First row from emps:" ); + printTable( r3 ); // 4) Scalar from emps - Object r4 = exec.execute("sql", "SELECT empid FROM emps LIMIT 1"); - System.out.println("First empid (scalar): " + r4); + Object r4 = exec.execute( "sql", "SELECT empid FROM emps LIMIT 1" ); + System.out.println( "First empid (scalar): " + r4 ); } finally { conn.close(); - System.exit(0); + System.exit( 0 ); } } - static void printTable(Object r) { - if (r instanceof Object[]) { + + static void printTable( Object r ) { + if ( r instanceof Object[] ) { Object[] t = (Object[]) r; // { colNames, data } String[] cols = (String[]) t[0]; Object[][] data = (Object[][]) t[1]; - System.out.println("Cols: " + Arrays.toString(cols)); - for (Object[] row : data) { - System.out.println(Arrays.toString(row)); + System.out.println( "Cols: " + Arrays.toString( cols ) ); + for ( Object[] row : data ) { + System.out.println( Arrays.toString( row ) ); } } else { - System.out.println(String.valueOf(r)); + System.out.println( String.valueOf( r ) ); } } + } diff --git a/app/src/main/java/execeuteQuery.m b/app/src/main/java/execeuteQuery.m deleted file mode 100644 index a4e0de2..0000000 --- a/app/src/main/java/execeuteQuery.m +++ /dev/null @@ -1 +0,0 @@ -// Matlab wrapper goes here \ No newline at end of file diff --git a/app/src/main/java/polyphenyconnector/PolyphenyConnection.java b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java new file mode 100644 index 0000000..6f4b716 --- /dev/null +++ b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java @@ -0,0 +1,155 @@ +package polyphenyconnector; + +import java.sql.*; + +public class PolyphenyConnection { + + private Connection connection; + private final String host, url, username, password; + private final int port; + + + /** + * @Description + * - Constructor supporting lazy-open: Stores logins; connects on first use to protect server resources. + * + * @param host: the host that should be used for the connection + * @param port: the port that should be used for the connection + * @param username: username to access the database with + * @param password: password to the corresponding username + * + * @return + * - Creates a PolyphenyConnection object that can be passed to the ExecuteQuery class and used to + * run queries + **/ + + public PolyphenyConnection( String host, int port, String username, String password ) { + this.host = host; + this.port = port; + this.url = "jdbc:polypheny://" + host + ":" + port; + this.username = username; + this.password = password; + this.connection = null; // The connection is established later on when needed. Lazy open + // prevents accidental resource leaks induced by user + } + + + /** + * @Description + * - Opens the server connection to Polypheny if needed (reuse otherwise). Checking the + * if-clause in java is a lot faster, than iterative opening and closing of the connection after every + * use for large numbers of queries, as it eliminates the ~10ms matlab-java crossover that opening and + * closing a connection from matlab would create. For 1M queries that avoids 1M*10ms = ~10 000sec=2.8 hrs + * of overhead. + * + **/ + public void openIfNeeded() { + if ( connection == null ) { + try { + connection = DriverManager.getConnection( url, username, password ); // runs startLocalPolypheny if connection isn't responsive + } catch ( SQLException e ) { + throw new RuntimeException( "Failed to open connection", e ); + } + } + } + + + /** + * @Description + * - Getter function for the host + * @return host The host passed to the PolyphenyConnection object. + */ + public String getHost() { + return host; + } + + + /** + * @Description + * - Getter function for the port + * @return port The port passed to the PolyphenyConnection object. + */ + public int getPort() { + return port; + } + + + /** + * @Description + * - Closes connection if open + * + **/ + public void close() { + try { + if ( connection != null && !connection.isClosed() ) { + connection.close(); + } + } catch ( SQLException e ) { + System.err.println( "Failed to close connection: " + e.getMessage() ); + } + } + + + /** + * @Description + * - Getter function for the connection variable of PolyphenyConnection + * + * @return + * - Connection connection variable of the PolyphenyConnection class + **/ + public Connection get_connection() { + return this.connection; + } + + + /** + * @Description + * - Setter function for the connection variable + * + * @param input_connection: The connection we want to set your PolyphenyConnection object to. + **/ + public void set_connection( Connection input_connection ) { + this.connection = input_connection; + } + + + /** + * @Description + * - Begins Database transaction. This is necessary to expose here because we need it to control flow in + * Batch queries handled in the QueryExecutor class later. + * + * @throws SQLException + */ + public void beginTransaction() throws SQLException { + openIfNeeded(); + connection.setAutoCommit( false ); + } + + + /** + * @Description + * - Commits Database transaction. This is necessary to expose here because we need it to control flow in + * Batch queries handled in the QueryExecutor class later. + * + * @throws SQLException + */ + public void commitTransaction() throws SQLException { + connection.commit(); + connection.setAutoCommit( true ); + + } + + + /** + * @Description + * - Rolls back Database transaction. This is necessary to expose here because we need it to control flow in + * Batch queries handled in the QueryExecutor class later. + * + * @throws SQLException + */ + public void rollbackTransaction() throws SQLException { + connection.rollback(); + connection.setAutoCommit( true ); + } + +} diff --git a/app/src/main/java/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java similarity index 99% rename from app/src/main/java/QueryExecutor.java rename to app/src/main/java/polyphenyconnector/QueryExecutor.java index 3e9edb6..e1636cd 100644 --- a/app/src/main/java/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -1,3 +1,5 @@ +package polyphenyconnector; + import java.sql.*; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/startup.m b/app/src/main/java/startup.m new file mode 100644 index 0000000..5d1850d --- /dev/null +++ b/app/src/main/java/startup.m @@ -0,0 +1,17 @@ +function startup + % Get root folder of the toolbox + root = fileparts( mfilename( 'fullpath' ) ); + + % Add Java JARs + javaaddpath( fullfile( root, 'jar', 'polypheny-all.jar' ) ); + javaaddpath( fullfile( root, 'libs', 'polypheny-jdbc-driver-2.3.jar' ) ); + + % Add MATLAB class folder to path + % addpath( fullfile( root, '+polypheny' ) ); % optional, allows tab-completion & easier access + + % Set Polypheny server jar path (for ProcessBuilder) + javaMethod( 'setProperty', 'java.lang.System', ... + 'polypheny.jar', fullfile( root, 'libs', 'polypheny.jar' ) ); + + disp('Polypheny connector initialized.'); +end From 3c707e19d5bc616f98ad4e5b5b726b74e979e588 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Fri, 12 Sep 2025 19:55:40 +0200 Subject: [PATCH 16/44] Added autocommit and cleaned up some things. Still need to push all the tests and the Matlabside. --- .../java/polyphenyconnector/PolyphenyConnection.java | 11 ++++------- .../main/java/polyphenyconnector/QueryExecutor.java | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/polyphenyconnector/PolyphenyConnection.java b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java index 6f4b716..eacc14c 100644 --- a/app/src/main/java/polyphenyconnector/PolyphenyConnection.java +++ b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java @@ -18,11 +18,7 @@ public class PolyphenyConnection { * @param username: username to access the database with * @param password: password to the corresponding username * - * @return - * - Creates a PolyphenyConnection object that can be passed to the ExecuteQuery class and used to - * run queries **/ - public PolyphenyConnection( String host, int port, String username, String password ) { this.host = host; this.port = port; @@ -46,7 +42,8 @@ public PolyphenyConnection( String host, int port, String username, String passw public void openIfNeeded() { if ( connection == null ) { try { - connection = DriverManager.getConnection( url, username, password ); // runs startLocalPolypheny if connection isn't responsive + connection = DriverManager.getConnection( url, username, password ); // runs startLocalPolypheny if connection isn't responsive + connection.setAutoCommit( true ); // make sure standard mode autocommits } catch ( SQLException e ) { throw new RuntimeException( "Failed to open connection", e ); } @@ -97,7 +94,7 @@ public void close() { * @return * - Connection connection variable of the PolyphenyConnection class **/ - public Connection get_connection() { + public Connection getConnection() { return this.connection; } @@ -108,7 +105,7 @@ public Connection get_connection() { * * @param input_connection: The connection we want to set your PolyphenyConnection object to. **/ - public void set_connection( Connection input_connection ) { + public void setConnection( Connection input_connection ) { this.connection = input_connection; } diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index e1636cd..7c60b1e 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -43,7 +43,7 @@ public Object execute( String language, String query ) { return null; case "sql": - try ( Statement stmt = polyconnection.get_connection().createStatement(); ResultSet rs = stmt.executeQuery( query ) ) { + try ( Statement stmt = polyconnection.getConnection().createStatement(); ResultSet rs = stmt.executeQuery( query ) ) { return ResultToMatlab( rs ); } catch ( Exception e ) { System.err.println( "SQL execution failed: " + e.getMessage() ); From 03b44e98a13415a2081d1b753d0d15eb561dd94d Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 13 Sep 2025 23:22:52 +0200 Subject: [PATCH 17/44] Puhsing all JUnit tests for QueryExecutor and PolyphenyConnection. --- .vscode/settings.json | 6 +- README.md | 5 +- app/build.gradle | 12 ++ app/src/main/java/Main.java | 3 +- app/src/main/java/QuickTest.java | 2 +- .../PolyphenyConnection.java | 15 +- .../polyphenyconnector/QueryExecutor.java | 6 +- .../PolyphenyConnectionTest.java | 109 ++++++++++++ .../PolyphenyConnectionTestHelper.java | 34 ++++ .../polyphenyconnector/QueryExecutorTest.java | 157 ++++++++++++++++++ 10 files changed, 330 insertions(+), 19 deletions(-) create mode 100644 app/src/test/java/polyphenyconnector/PolyphenyConnectionTest.java create mode 100644 app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java create mode 100644 app/src/test/java/polyphenyconnector/QueryExecutorTest.java diff --git a/.vscode/settings.json b/.vscode/settings.json index afa78c2..2b6c1a4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,14 +4,18 @@ ], "java.project.referencedLibraries": [], "cSpell.words": [ + "Afterall", "empid", "emps", "JDBC", "johnrengelman", "mongoql", + "myexecutor", "polyconnection", "Polypheny", - "polyphenyconnector" + "polyphenyconnector", + "wrongpass", + "wronguser" ], "java.format.enabled": true, "java.format.settings.url": ".vscode/Polypheny-Style.xml", diff --git a/README.md b/README.md index c7a6eaf..a1a8f00 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,7 @@ Addon for Matlab to connect and query a Polypheny database. @REQUIREMENTS: polypheny.jar must be in the lib folder -JDK17 (newer versions don't support the MatlabEngine yet https://www.mathworks.com/support/requirements/language-interfaces.html) \ No newline at end of file +JDK17 (newer versions don't support the MatlabEngine yet https://www.mathworks.com/support/requirements/language-interfaces.html) + +Tests: +Before the entire Project can be built it is necessary to run the Polypheny Application on the Local machine. Matlab natively uses Java 8. Consequently the Add-on is written in Java 8, but the polypheny.jar in supplied in the latest version is run with Java 17. To avoid version conflicts it is necessary to run the Polypheny Application separately before building the tests, otherwise the build will fail. Automating this startup within the project was considered, but ultimately rejected due to cross-platform instability and environment-specific issues. \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 0521ea3..f3f29ac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,6 +14,9 @@ dependencies { // JDBC driver: keep separate in toolbox/libs/, not bundled into the shaded JAR compileOnly files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) runtimeOnly files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) + + // JUnit 5 + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' } shadowJar { @@ -25,3 +28,12 @@ shadowJar { java { toolchain { languageVersion = JavaLanguageVersion.of(8) } } + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } + outputs.upToDateWhen { false } +} + diff --git a/app/src/main/java/Main.java b/app/src/main/java/Main.java index 11a422f..097592e 100644 --- a/app/src/main/java/Main.java +++ b/app/src/main/java/Main.java @@ -6,8 +6,7 @@ public class Main { public static void main( String[] args ) { try { String host = "localhost"; - int port = "public"; - String url = "jdbc:polypheny://localhost/public"; + int port = 205090; String user = "pa"; String pass = ""; diff --git a/app/src/main/java/QuickTest.java b/app/src/main/java/QuickTest.java index df7fad6..ae0bb9a 100644 --- a/app/src/main/java/QuickTest.java +++ b/app/src/main/java/QuickTest.java @@ -6,7 +6,7 @@ public class QuickTest { public static void main( String[] args ) throws Exception { - PolyphenyConnection conn = new PolyphenyConnection( "jdbc:polypheny://localhost:20590", "pa", "" ); + PolyphenyConnection conn = new PolyphenyConnection( "localhost", 20590, "pa", "" ); try { QueryExecutor exec = new QueryExecutor( conn ); diff --git a/app/src/main/java/polyphenyconnector/PolyphenyConnection.java b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java index eacc14c..306ac0c 100644 --- a/app/src/main/java/polyphenyconnector/PolyphenyConnection.java +++ b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java @@ -43,7 +43,7 @@ public void openIfNeeded() { if ( connection == null ) { try { connection = DriverManager.getConnection( url, username, password ); // runs startLocalPolypheny if connection isn't responsive - connection.setAutoCommit( true ); // make sure standard mode autocommits + connection.setAutoCommit( true ); // make sure standard mode defaults to AutoCommit } catch ( SQLException e ) { throw new RuntimeException( "Failed to open connection", e ); } @@ -83,6 +83,8 @@ public void close() { } } catch ( SQLException e ) { System.err.println( "Failed to close connection: " + e.getMessage() ); + } finally { + connection = null; } } @@ -99,17 +101,6 @@ public Connection getConnection() { } - /** - * @Description - * - Setter function for the connection variable - * - * @param input_connection: The connection we want to set your PolyphenyConnection object to. - **/ - public void setConnection( Connection input_connection ) { - this.connection = input_connection; - } - - /** * @Description * - Begins Database transaction. This is necessary to expose here because we need it to control flow in diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 7c60b1e..1f8cfc8 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -66,8 +66,8 @@ public Object execute( String language, String query ) { * * @param rs: The result object of the query of type ResultSet * - * @return: Result from the query which is either null/scalar/table (SQL), document (MongoQL) - * or TODO (Cypher) + * @return: Result from the query which is either null/scalar/table (SQL), TODO document (MongoQL) + * or (Cypher) **/ public Object ResultToMatlab( ResultSet rs ) throws Exception { @@ -110,8 +110,10 @@ public Object ResultToMatlab( ResultSet rs ) throws Exception { row[i] = rs.getObject( i + 1 ); // Saves each entry } rows.add( row ); // Append row to the List + System.err.println( "Fetched row: " + java.util.Arrays.toString( row ) ); } while ( rs.next() ); // First row already fetched above with rs.next() so we use do while + System.err.println( "Rows: " + java.util.Arrays.deepToString( rows.toArray() ) ); // Ensure that the colNames and rows have the same number of columns if ( colNames.length != rows.get( 0 ).length ) { throw new RuntimeException( "Mismatch: colNames and rowData column count don't match" ); diff --git a/app/src/test/java/polyphenyconnector/PolyphenyConnectionTest.java b/app/src/test/java/polyphenyconnector/PolyphenyConnectionTest.java new file mode 100644 index 0000000..e47f614 --- /dev/null +++ b/app/src/test/java/polyphenyconnector/PolyphenyConnectionTest.java @@ -0,0 +1,109 @@ +package polyphenyconnector; + +import java.sql.SQLException; +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; + +public class PolyphenyConnectionTest { + + private PolyphenyConnection conn; + private String host, username, password; + private int port; + + + @BeforeAll + static void waitForPolypheny() throws Exception { + PolyphenyConnectionTestHelper.waitForPolypheny(); + } + + + @BeforeEach + void setUp() { + host = "localhost"; + port = 20590; + username = "pa"; + password = ""; + conn = new PolyphenyConnection( host, port, username, password ); + } + + + @AfterEach + void tearDown() { + conn.close(); + } + + + @Test + void getHostPort() { + assertEquals( port, conn.getPort(), "Port must be 20590." ); + assertEquals( host, conn.getHost(), "Host must be localhost." ); + + } + + + @Test + void testLazyOpen() { + assertNull( conn.getConnection(), "Connection should start as null" ); + conn.openIfNeeded(); + assertNotNull( conn.getConnection(), "Connection should be established after openIfNeeded()" ); + } + + + @Test + void testBeginCommitRollback() throws SQLException { + conn.openIfNeeded(); + + // Test AutoCommit is enabled in the standard 1-Batch query case. This is important so myQueryExecutor.execute(...) can AutoCommit + assertTrue( conn.getConnection().getAutoCommit(), "AutoCommit should be true in the standard 1-Batch query case." ); + + // Test beginTransaction() actually disables the AutoCommit setting in openIfNeeded(). Important for its use in N-Batching. + conn.beginTransaction(); + assertFalse( conn.getConnection().getAutoCommit(), "AutoCommit should be false in transaction" ); + + // Test that AutoCommit is set to true again after commitTransaction() executes, so the standard 1-Batch query case can AutoCommit. + conn.commitTransaction(); + assertTrue( conn.getConnection().getAutoCommit(), "AutoCommit should be true after commit" ); + + // Test that after the rollbackTransaction() AutoCommit is true again, so the standard 1-Batch query case can AutoCommit. + conn.beginTransaction(); + conn.rollbackTransaction(); + assertTrue( conn.getConnection().getAutoCommit(), "AutoCommit should be true after rollback" ); + } + + + @Test + void testOpen() { + + // Test that openIfNeeded() twice doesn't change the existing connection. + conn.openIfNeeded(); + java.sql.Connection firstConnection = conn.getConnection(); + conn.close(); + conn.openIfNeeded(); + java.sql.Connection secondConnection = conn.getConnection(); + assertNotSame( firstConnection, secondConnection, "A new connection should be created after close()" ); + + // Test executing openIfNeeded() twice doesn't throw an Exception + assertDoesNotThrow( () -> conn.openIfNeeded(), "Opening twice should not throw an exception" ); + + } + + + @Test + void testClose() { + // Test that opening twice doesn't throw an exception + + conn.close(); + assertDoesNotThrow( () -> conn.close(), "Closing twice should not throw an exception" ); + assertNull( conn.getConnection(), "Connection should be null after close" ); + } + + + @Test + void testOpenWithInvalidCredentials() { + + // Tests that a RuntimeException is thrown. Makes sure false Username and Password are treated with an Exception + PolyphenyConnection badConn = new PolyphenyConnection( host, port, "wronguser", "wrongpass" ); + assertThrows( RuntimeException.class, badConn::openIfNeeded, "Opening with bad credentials should fail" ); + } + +} diff --git a/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java b/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java new file mode 100644 index 0000000..3c6cbce --- /dev/null +++ b/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java @@ -0,0 +1,34 @@ +package polyphenyconnector; + +import java.sql.DriverManager; + +public class PolyphenyConnectionTestHelper { + + static void waitForPolypheny() throws Exception { + String url = "jdbc:polypheny://localhost:20590"; + String user = "pa"; + String pass = ""; + + long deadline = System.currentTimeMillis() + 7000; // 7s timeout + int attempt = 1; + boolean ready = false; + + while ( System.currentTimeMillis() < deadline ) { + try ( java.sql.Connection conn = DriverManager.getConnection( url, user, pass ) ) { + if ( conn != null && !conn.isClosed() ) { + ready = true; + break; + } + } catch ( Exception e ) { + System.out.println( "Polypheny not ready (attempt " + attempt + ")" ); + } + attempt++; + Thread.sleep( 1000 ); // wait 1s before retry + } + + if ( !ready ) { + throw new RuntimeException( "Polypheny did not become available within 7 seconds." ); + } + } + +} diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java new file mode 100644 index 0000000..a2fe1db --- /dev/null +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -0,0 +1,157 @@ +package polyphenyconnector; + +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; + +public class QueryExecutorTest { + + private static PolyphenyConnection myconnection; + private static QueryExecutor myexecutor; + + + @BeforeAll + static void setUpNamespaceAndTable() throws Exception { + + // Wait for Polypheny to be available and connect to localhost. We do this because we run all the JUnit tests on our local machine. + PolyphenyConnectionTestHelper.waitForPolypheny(); + myconnection = new PolyphenyConnection( "localhost", 20590, "pa", "" ); + myexecutor = new QueryExecutor( myconnection ); + + // Delete any TABLE called and any NAMESPACE . This is important so we can insert it + // cleanly, in case tests break mid run the cleanup "@Afterall" might not have been executed properly. + myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "DROP NAMESPACE IF EXISTS unittest_namespace" ); + + // Creates the NAMESPACE and TABLE . + myexecutor.execute( "sql", "CREATE NAMESPACE unittest_namespace" ); + myexecutor.execute( "sql", "CREATE TABLE unittest_namespace.unittest_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY(id))" ); + } + + + @AfterAll + static void tearDownNamespaceAndTable() { + // Cleans up the TABLE and NAMESPACE we created so we leave no trace after the tests. + myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "DROP NAMESPACE IF EXISTS unittest_namespace" ); + myconnection.close(); + } + + // ───────────────────────────── + // Isolated branch tests (no table needed) + // ───────────────────────────── + + + @Test + void testScalarLiteral() { + Object result = myexecutor.execute( "sql", "SELECT 42 AS answer" ); + assertTrue( result instanceof Integer, "Expected an integer scalar" ); + assertEquals( 42, result ); + } + + + @BeforeEach + void clearTable() { + myexecutor.execute( "sql", "DELETE FROM unittest_namespace.unittest_table" ); + } + + + @Test + void testEmptyLiteral() { + Object result = myexecutor.execute( "sql", "SELECT * FROM (SELECT 1) t WHERE 1=0" ); + assertNull( result, "Query with no rows should return null" ); + } + + + @Test + void testTableLiteral() { + Object result = myexecutor.execute( "sql", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); + assertTrue( result instanceof Object[], "Expected tabular result" ); + + Object[] arr = (Object[]) result; + String[] colNames = (String[]) arr[0]; + Object[][] data = (Object[][]) arr[1]; + + assertArrayEquals( new String[]{ "a", "b" }, colNames, "Column names must match" ); + assertEquals( 2, data.length, "Should have 2 rows" ); + assertArrayEquals( new Object[]{ 1, 2 }, data[0] ); + assertArrayEquals( new Object[]{ 3, 4 }, data[1] ); + } + + // ───────────────────────────── + // Realistic integration tests (use unittest_namespace.unittest_table) + // ───────────────────────────── + + + @Test + void testInsertAndSelect() { + // Insert id = 1 and name = Alice into the table. + myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); + + // Query the result from the table. + Object result = myexecutor.execute( "sql", "SELECT id, name FROM unittest_namespace.unittest_table" ); + + // Test that the result comes back as array. + System.out.println( "Result is: " + result ); + assertTrue( result instanceof Object[], "Expected tabular result" ); + + // Test that the contents of the query are correct. + Object[] arr = (Object[]) result; + String[] colNames = (String[]) arr[0]; + Object[][] data = (Object[][]) arr[1]; + assertArrayEquals( new String[]{ "id", "name" }, colNames, "Column names must match" ); + assertEquals( 1, data.length, "Should have one row" ); + assertArrayEquals( new Object[]{ 1, "Alice" }, data[0], "Row must match inserted values" ); + + } + + + @Test + void testScalarFromTable() { + myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Carol')" ); + Object result = myexecutor.execute( "sql", "SELECT id FROM unittest_namespace.unittest_table WHERE name = 'Carol'" ); + assertTrue( result instanceof Integer, "Expected scalar integer result" ); + assertEquals( 2, result ); + } + + + @Test + void testInsertAndSelectMultipleRows() { + // Insert id = 1,2 and name = Alice, Bob into the table. + myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); + myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Bob')" ); + + // Query the result from the table. + Object result = myexecutor.execute( "sql", "SELECT id, name FROM unittest_namespace.unittest_table ORDER BY id" ); + + // Check the contents of the query are correct. + Object[] arr = (Object[]) result; + String[] colNames = (String[]) arr[0]; + Object[][] data = (Object[][]) arr[1]; + + // Test the column names match. + assertArrayEquals( new String[]{ "id", "name" }, colNames ); + + // Test the array has indeed length 2 (2 rows for Alice and Bob) + assertEquals( 2, data.length ); + + // Test the contents of each row are correct. + assertArrayEquals( new Object[]{ 1, "Alice" }, data[0] ); + assertArrayEquals( new Object[]{ 2, "Bob" }, data[1] ); + } + + + @Test + void testDeleteFromTable() { + + // Insert Bob into table. + myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Bob')" ); + + // Delete Bob from table. + myexecutor.execute( "sql", "DELETE FROM unittest_namespace.unittest_table" ); + + // Test that the query comes back null. + Object result = myexecutor.execute( "sql", "SELECT * FROM unittest_namespace.unittest_table WHERE name == 'Bob'" ); + assertNull( result, "After DELETE the table should be empty" ); + } + +} From 7f7d881b8e4a73abf2dd2d19292f6a51cb8d4a57 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sun, 14 Sep 2025 01:56:40 +0200 Subject: [PATCH 18/44] Changed the executeQuery in QueryExecutor.java and distinguished between executeQuery and executeUpdate. --- .../java/polyphenyconnector/QueryExecutor.java | 16 ++++++++++++---- .../polyphenyconnector/QueryExecutorTest.java | 15 ++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 1f8cfc8..178db30 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -43,11 +43,19 @@ public Object execute( String language, String query ) { return null; case "sql": - try ( Statement stmt = polyconnection.getConnection().createStatement(); ResultSet rs = stmt.executeQuery( query ) ) { - return ResultToMatlab( rs ); + try ( Statement stmt = polyconnection.getConnection().createStatement() ) { + String first = query.trim().toUpperCase(); + + if ( first.startsWith( "SELECT" ) ) { + try ( ResultSet rs = stmt.executeQuery( query ) ) { + return ResultToMatlab( rs ); + } + } else { + stmt.executeUpdate( query ); // INSERT, UPDATE, DELETE, CREATE, DROP, ... + return null; + } } catch ( Exception e ) { - System.err.println( "SQL execution failed: " + e.getMessage() ); - return null; + throw new RuntimeException( "SQL execution failed: " + e.getMessage(), e ); } case "mongoql": diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index a2fe1db..c31ae58 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -17,11 +17,16 @@ static void setUpNamespaceAndTable() throws Exception { myconnection = new PolyphenyConnection( "localhost", 20590, "pa", "" ); myexecutor = new QueryExecutor( myconnection ); - // Delete any TABLE called and any NAMESPACE . This is important so we can insert it + // Delete any TABLE called and any NAMESPACE if it exists. This is important so we can insert it // cleanly, in case tests break mid run the cleanup "@Afterall" might not have been executed properly. - myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); - myexecutor.execute( "sql", "DROP NAMESPACE IF EXISTS unittest_namespace" ); - + try { + myexecutor.execute( "sql", "DROP TABLE unittest_namespace.unittest_table" ); + } catch ( Exception ignored ) { + } + try { + myexecutor.execute( "sql", "DROP NAMESPACE unittest_namespace" ); + } catch ( Exception ignored ) { + } // Creates the NAMESPACE and TABLE . myexecutor.execute( "sql", "CREATE NAMESPACE unittest_namespace" ); myexecutor.execute( "sql", "CREATE TABLE unittest_namespace.unittest_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY(id))" ); @@ -150,7 +155,7 @@ void testDeleteFromTable() { myexecutor.execute( "sql", "DELETE FROM unittest_namespace.unittest_table" ); // Test that the query comes back null. - Object result = myexecutor.execute( "sql", "SELECT * FROM unittest_namespace.unittest_table WHERE name == 'Bob'" ); + Object result = myexecutor.execute( "sql", "SELECT * FROM unittest_namespace.unittest_table WHERE name = 'Bob'" ); assertNull( result, "After DELETE the table should be empty" ); } From 8cb68a635e077c7880d0384c5cac8acb2d9d2894 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sun, 14 Sep 2025 02:03:18 +0200 Subject: [PATCH 19/44] Added another test QueryWithSpaces to test if queries now still work if theres spaces in the query String. --- .../polyphenyconnector/QueryExecutorTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index c31ae58..5e11a75 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -145,6 +145,33 @@ void testInsertAndSelectMultipleRows() { } + @Test + void testQueryWithSpaces() { + // Insert Bob into table. + // Insert id = 1,2 and name = Alice, Bob into the table. + myexecutor.execute( "sql", " INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); + myexecutor.execute( "sql", " INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Bob')" ); + + // Query the result from the table. + Object result = myexecutor.execute( "sql", "SELECT id, name FROM unittest_namespace.unittest_table ORDER BY id" ); + + // Check the contents of the query are correct. + Object[] arr = (Object[]) result; + String[] colNames = (String[]) arr[0]; + Object[][] data = (Object[][]) arr[1]; + + // Test the column names match. + assertArrayEquals( new String[]{ "id", "name" }, colNames ); + + // Test the array has indeed length 2 (2 rows for Alice and Bob) + assertEquals( 2, data.length ); + + // Test the contents of each row are correct. + assertArrayEquals( new Object[]{ 1, "Alice" }, data[0] ); + assertArrayEquals( new Object[]{ 2, "Bob" }, data[1] ); + } + + @Test void testDeleteFromTable() { From 13851ea4b510f4acc5b32a76d035a6d821ac8352 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sun, 14 Sep 2025 19:50:40 +0200 Subject: [PATCH 20/44] Adapted the Wraper and added tests that test the Matlabside. Everything passes now and the startup works. Publish for the relational part almost ready. --- LICENSE | 2 +- app/bin/main/MatlabTest.m | 8 --- app/bin/main/Polypheny.m | 33 +++++++------ app/bin/main/PolyphenyWrapperTest.m | 49 +++++++++++++++++++ app/bin/main/startup.m | 34 +++++++++---- app/src/main/java/MatlabTest.m | 8 --- app/src/main/java/Polypheny.m | 33 +++++++------ app/src/main/java/PolyphenyWrapperTest.m | 49 +++++++++++++++++++ .../PolyphenyConnection.java | 2 +- .../polyphenyconnector/QueryExecutor.java | 4 +- app/src/main/java/startup.m | 34 +++++++++---- 11 files changed, 188 insertions(+), 68 deletions(-) delete mode 100644 app/bin/main/MatlabTest.m create mode 100644 app/bin/main/PolyphenyWrapperTest.m delete mode 100644 app/src/main/java/MatlabTest.m create mode 100644 app/src/main/java/PolyphenyWrapperTest.m diff --git a/LICENSE b/LICENSE index 261eeb9..845ef47 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright [2025] [Fynn Gohlke] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/app/bin/main/MatlabTest.m b/app/bin/main/MatlabTest.m deleted file mode 100644 index 8470642..0000000 --- a/app/bin/main/MatlabTest.m +++ /dev/null @@ -1,8 +0,0 @@ -startup(); - -p = polypheny.Polypheny('sql', 'jdbc:polypheny://localhost:20590', 'pa', ''); - -T = p.query("SELECT 1 AS test_column"); -disp(T); - -p.close(); diff --git a/app/bin/main/Polypheny.m b/app/bin/main/Polypheny.m index 6c6cc55..2ae69e6 100644 --- a/app/bin/main/Polypheny.m +++ b/app/bin/main/Polypheny.m @@ -10,18 +10,18 @@ methods - % - function PolyWrapper = Polypheny(language, url, user, password ) - % Polypheny( LANGUAGE, URL, USER, PASSWORD ): Set up Java connection + executor - % LANGUAGE: The database language used for the query. Must be sql, mongoql or cypher. - % URL: The database url - % USER: The username - % PASSWORD: The password - % Polywrapper: A Matlab object that is a wrapper for the two java classes - % QueryExecutioner.java and PolyphenyConnection.java + + function PolyWrapper = Polypheny( language, host, port, user, password ) + % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor + % LANGUAGE: The database language ('sql', 'mongoql', 'cypher') + % HOST: Database host (e.g. 'localhost') + % PORT: Database port (integer) + % USER: Username + % PASSWORD: Password + PolyWrapper.language = language; - PolyWrapper.polyConnection = javaObject( "polyphenyconnector.PolyphenyConnection", url, user, password ); - PolyWrapper.queryExecutor = javaObject( "polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); + PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); end @@ -39,11 +39,14 @@ elseif isscalar( r ) T = r; + elseif isa(r,'java.lang.Object[]') && numel(r) == 2 + r = cell(r); + colNames = cell(r{1}); + data = cell(r{2}); % Object[][] → MATLAB cell + T = cell2table(data, 'VariableNames', colNames); + else - % Expect Java side to return { colNames, data } - colNames = cell( r(1) ); - data = r(2); - T = cell2table( data, 'VariableNames', colNames ); + T = []; % fallback to avoid cell2table crash end end diff --git a/app/bin/main/PolyphenyWrapperTest.m b/app/bin/main/PolyphenyWrapperTest.m new file mode 100644 index 0000000..9613888 --- /dev/null +++ b/app/bin/main/PolyphenyWrapperTest.m @@ -0,0 +1,49 @@ +classdef PolyphenyWrapperTest < matlab.unittest.TestCase + properties + conn + end + + methods (TestMethodSetup) + function setupConnection(testCase) + startup; + testCase.conn = polypheny.Polypheny( ... + 'sql', 'localhost', int32(20590), 'pa', '' ); + end + end + + methods (TestMethodTeardown) + function closeConnection(testCase) + testCase.conn.close(); + end + end + + methods (Test) + function testScalar(testCase) + r = testCase.conn.query("SELECT 1 AS x"); + testCase.verifyEqual(r, 1); + end + + function testTable(testCase) + testCase.conn.query("DROP TABLE IF EXISTS wrapper_test"); + testCase.conn.query("CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); + + T = testCase.conn.query("SELECT * FROM wrapper_test ORDER BY id"); + + if istable(T) + % Expected: table output with column "name" + testCase.verifyEqual(T.name, {'Alice'; 'Bob'}); + elseif iscell(T) + % Fallback: check the raw cell contents + testCase.verifyEqual(T(:,2), {'Alice','Bob'}'); + else + testCase.verifyFail("Unexpected return type: " + class(T)); + end + end + + function testEmpty(testCase) + T = testCase.conn.query("SELECT * FROM wrapper_test WHERE id=999"); + testCase.verifyEmpty(T); + end + end +end diff --git a/app/bin/main/startup.m b/app/bin/main/startup.m index 5d1850d..c4dad7b 100644 --- a/app/bin/main/startup.m +++ b/app/bin/main/startup.m @@ -1,17 +1,33 @@ function startup % Get root folder of the toolbox - root = fileparts( mfilename( 'fullpath' ) ); + root = fileparts(mfilename('fullpath')); - % Add Java JARs - javaaddpath( fullfile( root, 'jar', 'polypheny-all.jar' ) ); - javaaddpath( fullfile( root, 'libs', 'polypheny-jdbc-driver-2.3.jar' ) ); + % Paths to JARs + jarPaths = { ... + fullfile(root, 'jar', 'polypheny-all.jar'), ... + fullfile(root, 'libs', 'polypheny-jdbc-driver-2.3.jar') ... + }; - % Add MATLAB class folder to path - % addpath( fullfile( root, '+polypheny' ) ); % optional, allows tab-completion & easier access + % Add JARs if not already on classpath + for i = 1:numel(jarPaths) + if ~any(strcmp(jarPaths{i}, javaclasspath('-all'))) + javaaddpath(jarPaths{i}); + end + end - % Set Polypheny server jar path (for ProcessBuilder) - javaMethod( 'setProperty', 'java.lang.System', ... - 'polypheny.jar', fullfile( root, 'libs', 'polypheny.jar' ) ); + % Try to register the JDBC driver dynamically + try + %java.lang.Class.forName('org.polypheny.jdbc.PolyphenyDriver'); + driver = javaObject('org.polypheny.jdbc.PolyphenyDriver'); + java.sql.DriverManager.registerDriver(driver); + catch e + warning('Could not register Polypheny JDBC driver dynamically: %s', char(e.message)); + end + + % Add MATLAB namespace folder (+polypheny) + if exist(fullfile(root, '+polypheny'), 'dir') + addpath(root); + end disp('Polypheny connector initialized.'); end diff --git a/app/src/main/java/MatlabTest.m b/app/src/main/java/MatlabTest.m deleted file mode 100644 index 8470642..0000000 --- a/app/src/main/java/MatlabTest.m +++ /dev/null @@ -1,8 +0,0 @@ -startup(); - -p = polypheny.Polypheny('sql', 'jdbc:polypheny://localhost:20590', 'pa', ''); - -T = p.query("SELECT 1 AS test_column"); -disp(T); - -p.close(); diff --git a/app/src/main/java/Polypheny.m b/app/src/main/java/Polypheny.m index 6c6cc55..2ae69e6 100644 --- a/app/src/main/java/Polypheny.m +++ b/app/src/main/java/Polypheny.m @@ -10,18 +10,18 @@ methods - % - function PolyWrapper = Polypheny(language, url, user, password ) - % Polypheny( LANGUAGE, URL, USER, PASSWORD ): Set up Java connection + executor - % LANGUAGE: The database language used for the query. Must be sql, mongoql or cypher. - % URL: The database url - % USER: The username - % PASSWORD: The password - % Polywrapper: A Matlab object that is a wrapper for the two java classes - % QueryExecutioner.java and PolyphenyConnection.java + + function PolyWrapper = Polypheny( language, host, port, user, password ) + % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor + % LANGUAGE: The database language ('sql', 'mongoql', 'cypher') + % HOST: Database host (e.g. 'localhost') + % PORT: Database port (integer) + % USER: Username + % PASSWORD: Password + PolyWrapper.language = language; - PolyWrapper.polyConnection = javaObject( "polyphenyconnector.PolyphenyConnection", url, user, password ); - PolyWrapper.queryExecutor = javaObject( "polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); + PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); end @@ -39,11 +39,14 @@ elseif isscalar( r ) T = r; + elseif isa(r,'java.lang.Object[]') && numel(r) == 2 + r = cell(r); + colNames = cell(r{1}); + data = cell(r{2}); % Object[][] → MATLAB cell + T = cell2table(data, 'VariableNames', colNames); + else - % Expect Java side to return { colNames, data } - colNames = cell( r(1) ); - data = r(2); - T = cell2table( data, 'VariableNames', colNames ); + T = []; % fallback to avoid cell2table crash end end diff --git a/app/src/main/java/PolyphenyWrapperTest.m b/app/src/main/java/PolyphenyWrapperTest.m new file mode 100644 index 0000000..9613888 --- /dev/null +++ b/app/src/main/java/PolyphenyWrapperTest.m @@ -0,0 +1,49 @@ +classdef PolyphenyWrapperTest < matlab.unittest.TestCase + properties + conn + end + + methods (TestMethodSetup) + function setupConnection(testCase) + startup; + testCase.conn = polypheny.Polypheny( ... + 'sql', 'localhost', int32(20590), 'pa', '' ); + end + end + + methods (TestMethodTeardown) + function closeConnection(testCase) + testCase.conn.close(); + end + end + + methods (Test) + function testScalar(testCase) + r = testCase.conn.query("SELECT 1 AS x"); + testCase.verifyEqual(r, 1); + end + + function testTable(testCase) + testCase.conn.query("DROP TABLE IF EXISTS wrapper_test"); + testCase.conn.query("CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); + + T = testCase.conn.query("SELECT * FROM wrapper_test ORDER BY id"); + + if istable(T) + % Expected: table output with column "name" + testCase.verifyEqual(T.name, {'Alice'; 'Bob'}); + elseif iscell(T) + % Fallback: check the raw cell contents + testCase.verifyEqual(T(:,2), {'Alice','Bob'}'); + else + testCase.verifyFail("Unexpected return type: " + class(T)); + end + end + + function testEmpty(testCase) + T = testCase.conn.query("SELECT * FROM wrapper_test WHERE id=999"); + testCase.verifyEmpty(T); + end + end +end diff --git a/app/src/main/java/polyphenyconnector/PolyphenyConnection.java b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java index 306ac0c..a5dccb3 100644 --- a/app/src/main/java/polyphenyconnector/PolyphenyConnection.java +++ b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java @@ -42,7 +42,7 @@ public PolyphenyConnection( String host, int port, String username, String passw public void openIfNeeded() { if ( connection == null ) { try { - connection = DriverManager.getConnection( url, username, password ); // runs startLocalPolypheny if connection isn't responsive + connection = DriverManager.getConnection( url, username, password ); connection.setAutoCommit( true ); // make sure standard mode defaults to AutoCommit } catch ( SQLException e ) { throw new RuntimeException( "Failed to open connection", e ); diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 178db30..4e5991d 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -118,10 +118,10 @@ public Object ResultToMatlab( ResultSet rs ) throws Exception { row[i] = rs.getObject( i + 1 ); // Saves each entry } rows.add( row ); // Append row to the List - System.err.println( "Fetched row: " + java.util.Arrays.toString( row ) ); + // System.err.println( "Fetched row: " + java.util.Arrays.toString( row ) ); } while ( rs.next() ); // First row already fetched above with rs.next() so we use do while - System.err.println( "Rows: " + java.util.Arrays.deepToString( rows.toArray() ) ); + // System.err.println( "Rows: " + java.util.Arrays.deepToString( rows.toArray() ) ); // Ensure that the colNames and rows have the same number of columns if ( colNames.length != rows.get( 0 ).length ) { throw new RuntimeException( "Mismatch: colNames and rowData column count don't match" ); diff --git a/app/src/main/java/startup.m b/app/src/main/java/startup.m index 5d1850d..c4dad7b 100644 --- a/app/src/main/java/startup.m +++ b/app/src/main/java/startup.m @@ -1,17 +1,33 @@ function startup % Get root folder of the toolbox - root = fileparts( mfilename( 'fullpath' ) ); + root = fileparts(mfilename('fullpath')); - % Add Java JARs - javaaddpath( fullfile( root, 'jar', 'polypheny-all.jar' ) ); - javaaddpath( fullfile( root, 'libs', 'polypheny-jdbc-driver-2.3.jar' ) ); + % Paths to JARs + jarPaths = { ... + fullfile(root, 'jar', 'polypheny-all.jar'), ... + fullfile(root, 'libs', 'polypheny-jdbc-driver-2.3.jar') ... + }; - % Add MATLAB class folder to path - % addpath( fullfile( root, '+polypheny' ) ); % optional, allows tab-completion & easier access + % Add JARs if not already on classpath + for i = 1:numel(jarPaths) + if ~any(strcmp(jarPaths{i}, javaclasspath('-all'))) + javaaddpath(jarPaths{i}); + end + end - % Set Polypheny server jar path (for ProcessBuilder) - javaMethod( 'setProperty', 'java.lang.System', ... - 'polypheny.jar', fullfile( root, 'libs', 'polypheny.jar' ) ); + % Try to register the JDBC driver dynamically + try + %java.lang.Class.forName('org.polypheny.jdbc.PolyphenyDriver'); + driver = javaObject('org.polypheny.jdbc.PolyphenyDriver'); + java.sql.DriverManager.registerDriver(driver); + catch e + warning('Could not register Polypheny JDBC driver dynamically: %s', char(e.message)); + end + + % Add MATLAB namespace folder (+polypheny) + if exist(fullfile(root, '+polypheny'), 'dir') + addpath(root); + end disp('Polypheny connector initialized.'); end From 7a97e8567f6fa6c40309cbf96541eebef08c2a60 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Mon, 15 Sep 2025 02:28:12 +0200 Subject: [PATCH 21/44] Added the N-Batch queries option into QueryExecutor.java --- app/src/main/java/QuickTest.java | 1 - .../polyphenyconnector/QueryExecutor.java | 62 ++++++++++++++-- .../polyphenyconnector/QueryExecutorTest.java | 73 ++++++++++++++++--- 3 files changed, 117 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/QuickTest.java b/app/src/main/java/QuickTest.java index ae0bb9a..ee9810d 100644 --- a/app/src/main/java/QuickTest.java +++ b/app/src/main/java/QuickTest.java @@ -18,7 +18,6 @@ public static void main( String[] args ) throws Exception { Object r2 = exec.execute( "sql", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); printTable( r2 ); - //TODO: change the empid test to something thats independent of that. // 3) First row from emps (1-row table) Object r3 = exec.execute( "sql", "SELECT * FROM emps LIMIT 1" ); System.out.println( "First row from emps:" ); diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 4e5991d..0669c56 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -15,8 +15,7 @@ public class QueryExecutor { * * @param polyconnection: PolyphenyConnection object that holds the connection * details to the Database. It's used to execute queries - * @return: Object of the query (SQL: empty, scalar or table; MongoQL: TODO; - * Cypher: TODO) + * @return: Object of the query (SQL: empty, scalar or table; MongoQL: TODO; Cypher: TODO) **/ public QueryExecutor( PolyphenyConnection polyconnection ) { this.polyconnection = polyconnection; @@ -27,12 +26,10 @@ public QueryExecutor( PolyphenyConnection polyconnection ) { * @Description * - Executes the query depending on the language given by the user * - * @param language: The database language that is used (e.g. SQL, MongoQL, - * Cypher) + * @param language: The database language that is used (e.g. SQL, MongoQL,Cypher) * @param query: The query-text to be executed (e.g. FROM emps SELECT *) * - * @return: ResultToMatlab(rs) which is a Matlab compatible object that is cast - * to the Matlab user. + * @return: ResultToMatlab(rs) which is a Matlab compatible object that is cast to the Matlab user. **/ public Object execute( String language, String query ) { @@ -67,6 +64,56 @@ public Object execute( String language, String query ) { } + /** + * @Description + * This function is capable of executing a List of non-SELECT SQL statements in one single Matlab-Java crossing. All SQL statements + * except SELECT are supported. For further information consult the Polyphenys JDBC Driver documentation. + * + * @param language The database language that is used (e.g. SQL, MongoQL,Cypher) + * @param query_list The list of query strings to be executed. + * @return int[] result An array of integers, where the i-th entry will denote for the i-th query how many rows were touched, e.g. + * n: n rows were updated, 0: no rows were touched. + */ + public int[] executeBatch( String language, List query_list ) { + + polyconnection.openIfNeeded(); + + switch ( language.toLowerCase() ) { + default: + System.err.println( "Unsupported language: " + language ); + + case "sql": + try { + polyconnection.beginTransaction(); + try ( Statement stmt = polyconnection.getConnection().createStatement() ) { + for ( String query : query_list ) { + String first = query.trim().toUpperCase(); + if ( first.startsWith( "SELECT" ) ) { + throw new UnsupportedOperationException( "Batch execution does not support SELECT statements." ); + } + stmt.addBatch( query ); + } + int[] result = stmt.executeBatch(); + polyconnection.commitTransaction(); + return result; + } catch ( Exception e ) { + polyconnection.rollbackTransaction(); + throw new RuntimeException( "SQL batch execution failed. Transaction was rolled back: " + e.getMessage(), e ); + } + } catch ( SQLException e ) { + throw new RuntimeException( "Failed to manage transaction", e ); + } + + case "mongoql": + throw new UnsupportedOperationException( "MongoQL batch execution not yet implemented." ); + + case "cypher": + throw new UnsupportedOperationException( "Cypher batch execution not yet implemented." ); + } + + } + + /** * @Description * - Casts the result of the queries to MatlabObjects, depending on @@ -74,8 +121,7 @@ public Object execute( String language, String query ) { * * @param rs: The result object of the query of type ResultSet * - * @return: Result from the query which is either null/scalar/table (SQL), TODO document (MongoQL) - * or (Cypher) + * @return: Result from the query which is either null/scalar/table (SQL), TODO document (MongoQL, or (Cypher) **/ public Object ResultToMatlab( ResultSet rs ) throws Exception { diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index 5e11a75..3a78642 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -3,6 +3,9 @@ import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; +import java.util.Arrays; +import java.util.List; + public class QueryExecutorTest { private static PolyphenyConnection myconnection; @@ -14,22 +17,31 @@ static void setUpNamespaceAndTable() throws Exception { // Wait for Polypheny to be available and connect to localhost. We do this because we run all the JUnit tests on our local machine. PolyphenyConnectionTestHelper.waitForPolypheny(); + Thread.sleep( 4000 ); myconnection = new PolyphenyConnection( "localhost", 20590, "pa", "" ); myexecutor = new QueryExecutor( myconnection ); + // 1. Setup tables for .execute() // Delete any TABLE called and any NAMESPACE if it exists. This is important so we can insert it // cleanly, in case tests break mid run the cleanup "@Afterall" might not have been executed properly. try { - myexecutor.execute( "sql", "DROP TABLE unittest_namespace.unittest_table" ); - } catch ( Exception ignored ) { - } - try { + myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); myexecutor.execute( "sql", "DROP NAMESPACE unittest_namespace" ); } catch ( Exception ignored ) { } + // Creates the NAMESPACE and TABLE . myexecutor.execute( "sql", "CREATE NAMESPACE unittest_namespace" ); myexecutor.execute( "sql", "CREATE TABLE unittest_namespace.unittest_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY(id))" ); + + // 2. Setup tables for executeBatch() + // Delete any tables that might still exist as described before. + try { + myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.batch_table" ); + } catch ( Exception ignored ) { + } + myexecutor.execute( "sql", "CREATE TABLE unittest_namespace.batch_table (" + "emp_id INT NOT NULL, " + "name VARCHAR(100), " + "gender VARCHAR(10), " + "birthday DATE, " + "employee_id INT, " + "PRIMARY KEY(emp_id))" ); + } @@ -37,10 +49,25 @@ static void setUpNamespaceAndTable() throws Exception { static void tearDownNamespaceAndTable() { // Cleans up the TABLE and NAMESPACE we created so we leave no trace after the tests. myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.batch_table" ); myexecutor.execute( "sql", "DROP NAMESPACE IF EXISTS unittest_namespace" ); myconnection.close(); } + + @BeforeEach + void clearTable() { + myexecutor.execute( "sql", "DELETE FROM unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "DELETE FROM unittest_namespace.batch_table" ); + } + + + @AfterEach + void clearTableAfter() { + myexecutor.execute( "sql", "DELETE FROM unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "DELETE FROM unittest_namespace.batch_table" ); + } + // ───────────────────────────── // Isolated branch tests (no table needed) // ───────────────────────────── @@ -54,12 +81,6 @@ void testScalarLiteral() { } - @BeforeEach - void clearTable() { - myexecutor.execute( "sql", "DELETE FROM unittest_namespace.unittest_table" ); - } - - @Test void testEmptyLiteral() { Object result = myexecutor.execute( "sql", "SELECT * FROM (SELECT 1) t WHERE 1=0" ); @@ -186,4 +207,36 @@ void testDeleteFromTable() { assertNull( result, "After DELETE the table should be empty" ); } + + @Test + void testBatchInsertEmployees() { + List queries = Arrays.asList( + "INSERT INTO unittest_namespace.batch_table VALUES (1, 'Alice', 'F', DATE '1990-01-15', 1001)", + "INSERT INTO unittest_namespace.batch_table VALUES (2, 'Bob', 'M', DATE '1989-05-12', 1002)", + "INSERT INTO unittest_namespace.batch_table VALUES (3, 'Jane', 'F', DATE '1992-07-23', 1003)", + "INSERT INTO unittest_namespace.batch_table VALUES (4, 'Tim', 'M', DATE '1991-03-03', 1004)", + "INSERT INTO unittest_namespace.batch_table VALUES (5, 'Alex', 'M', DATE '1994-11-11', 1005)", + "INSERT INTO unittest_namespace.batch_table VALUES (6, 'Mason', 'M', DATE '1988-04-22', 1006)", + "INSERT INTO unittest_namespace.batch_table VALUES (7, 'Rena', 'F', DATE '1995-06-17', 1007)", + "INSERT INTO unittest_namespace.batch_table VALUES (8, 'Christopher', 'M', DATE '1987-08-09', 1008)", + "INSERT INTO unittest_namespace.batch_table VALUES (9, 'Lexi', 'F', DATE '1996-09-30', 1009)", + "INSERT INTO unittest_namespace.batch_table VALUES (10, 'Baen', 'M', DATE '1990-10-05', 1010)", + "INSERT INTO unittest_namespace.batch_table VALUES (11, 'Ricardo', 'M', DATE '1986-12-12', 1011)", + "INSERT INTO unittest_namespace.batch_table VALUES (12, 'Tim', 'M', DATE '1993-02-02', 1012)", + "INSERT INTO unittest_namespace.batch_table VALUES (13, 'Beya', 'F', DATE '1994-05-25', 1013)" + ); + + int[] counts = myexecutor.executeBatch( "sql", queries ); + + assertEquals( 13, counts.length, "Batch should return 13 results" ); + for ( int c : counts ) { + assertEquals( 1, c, "Each INSERT should affect exactly 1 row" ); + } + + Object result = myexecutor.execute( "sql", "SELECT COUNT(*) FROM unittest_namespace.batch_table" ); + assertTrue( result instanceof Long || result instanceof Integer ); + int rowCount = ((Number) result).intValue(); + assertEquals( 13, rowCount, "Table should contain 13 rows after batch insert" ); + } + } From 469a236f6be1181fb6c63d576b58880befb3e910 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Mon, 15 Sep 2025 03:27:57 +0200 Subject: [PATCH 22/44] Added tests for the Matlabside of executeBatch() --- app/bin/main/ClassTest.m | 9 +++++++++ app/bin/main/Polypheny.m | 20 +++++++++++++++++++- app/bin/main/PolyphenyWrapperTest.m | 21 +++++++++++++++++++++ app/src/main/java/ClassTest.m | 9 +++++++++ app/src/main/java/Polypheny.m | 20 +++++++++++++++++++- app/src/main/java/PolyphenyWrapperTest.m | 21 +++++++++++++++++++++ 6 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 app/bin/main/ClassTest.m create mode 100644 app/src/main/java/ClassTest.m diff --git a/app/bin/main/ClassTest.m b/app/bin/main/ClassTest.m new file mode 100644 index 0000000..47f32fc --- /dev/null +++ b/app/bin/main/ClassTest.m @@ -0,0 +1,9 @@ +clear all; clear classes +%conn = javaObject('polyphenyconnector.PolyphenyConnection', 'localhost', int32(20590), 'pa', ''); +%exec = javaObject('polyphenyconnector.QueryExecutor', conn); + +%res = exec.execute('sql', 'SELECT 1 AS x'); +results = runtests('PolyphenyWrapperTest'); +disp(results) + +%disp(res); diff --git a/app/bin/main/Polypheny.m b/app/bin/main/Polypheny.m index 2ae69e6..dd79b8c 100644 --- a/app/bin/main/Polypheny.m +++ b/app/bin/main/Polypheny.m @@ -1,6 +1,6 @@ classdef Polypheny < handle % POLYPHENY MATLAB wrapper for the Polypheny Java connector. Wraps polyphenyconnector.PolyphenyConnection -% and QueryExecutor to run queries from MATLAB +% and polyphenyconnector.QueryExecutor to run queries from MATLAB properties (Access = private) language % 'sql' | 'mongoql' | 'cypher' polyConnection % Java PolyphenyConnection @@ -49,6 +49,24 @@ T = []; % fallback to avoid cell2table crash end end + + function result = queryBatch( PolyWrapper, queryList ) + % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements + % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) + % + % Returns: int array with rows affected per statement + + if ~iscell( queryList ) % cell is the Matlab List type + error( 'queryBatch expects a cell array of SQL strings' ); + end + + javaList = java.util.ArrayList(); + for i = 1:numel(queryList) + javaList.add( string(queryList{i}) ); + end + java_result = PolyWrapper.queryExecutor.executeBatch( string(PolyWrapper.language), javaList ); + result = double(java_result(:))'; + end function close( PolyWrapper ) % close( POLYWRAPPER ): Close the Java connection diff --git a/app/bin/main/PolyphenyWrapperTest.m b/app/bin/main/PolyphenyWrapperTest.m index 9613888..b44ffbd 100644 --- a/app/bin/main/PolyphenyWrapperTest.m +++ b/app/bin/main/PolyphenyWrapperTest.m @@ -44,6 +44,27 @@ function testTable(testCase) function testEmpty(testCase) T = testCase.conn.query("SELECT * FROM wrapper_test WHERE id=999"); testCase.verifyEmpty(T); + end + + function testBatchInsert(testCase) + % Prepare table + testCase.conn.query("DROP TABLE IF EXISTS batch_test"); + testCase.conn.query("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + + % Batch insert 2 rows + queries = { ... + "INSERT INTO batch_test VALUES (1,'Alice')", ... + "INSERT INTO batch_test VALUES (2,'Bob')" ... + }; + res = testCase.conn.queryBatch(queries); + + % Verify JDBC return codes + testCase.verifyEqual(res, [1 1]); + + % Verify table contents + T = testCase.conn.query("SELECT id, name FROM batch_test ORDER BY id"); + testCase.verifyEqual(T.id, [1; 2]); + testCase.verifyEqual(string(T.name), ["Alice"; "Bob"]); end end end diff --git a/app/src/main/java/ClassTest.m b/app/src/main/java/ClassTest.m new file mode 100644 index 0000000..47f32fc --- /dev/null +++ b/app/src/main/java/ClassTest.m @@ -0,0 +1,9 @@ +clear all; clear classes +%conn = javaObject('polyphenyconnector.PolyphenyConnection', 'localhost', int32(20590), 'pa', ''); +%exec = javaObject('polyphenyconnector.QueryExecutor', conn); + +%res = exec.execute('sql', 'SELECT 1 AS x'); +results = runtests('PolyphenyWrapperTest'); +disp(results) + +%disp(res); diff --git a/app/src/main/java/Polypheny.m b/app/src/main/java/Polypheny.m index 2ae69e6..dd79b8c 100644 --- a/app/src/main/java/Polypheny.m +++ b/app/src/main/java/Polypheny.m @@ -1,6 +1,6 @@ classdef Polypheny < handle % POLYPHENY MATLAB wrapper for the Polypheny Java connector. Wraps polyphenyconnector.PolyphenyConnection -% and QueryExecutor to run queries from MATLAB +% and polyphenyconnector.QueryExecutor to run queries from MATLAB properties (Access = private) language % 'sql' | 'mongoql' | 'cypher' polyConnection % Java PolyphenyConnection @@ -49,6 +49,24 @@ T = []; % fallback to avoid cell2table crash end end + + function result = queryBatch( PolyWrapper, queryList ) + % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements + % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) + % + % Returns: int array with rows affected per statement + + if ~iscell( queryList ) % cell is the Matlab List type + error( 'queryBatch expects a cell array of SQL strings' ); + end + + javaList = java.util.ArrayList(); + for i = 1:numel(queryList) + javaList.add( string(queryList{i}) ); + end + java_result = PolyWrapper.queryExecutor.executeBatch( string(PolyWrapper.language), javaList ); + result = double(java_result(:))'; + end function close( PolyWrapper ) % close( POLYWRAPPER ): Close the Java connection diff --git a/app/src/main/java/PolyphenyWrapperTest.m b/app/src/main/java/PolyphenyWrapperTest.m index 9613888..b44ffbd 100644 --- a/app/src/main/java/PolyphenyWrapperTest.m +++ b/app/src/main/java/PolyphenyWrapperTest.m @@ -44,6 +44,27 @@ function testTable(testCase) function testEmpty(testCase) T = testCase.conn.query("SELECT * FROM wrapper_test WHERE id=999"); testCase.verifyEmpty(T); + end + + function testBatchInsert(testCase) + % Prepare table + testCase.conn.query("DROP TABLE IF EXISTS batch_test"); + testCase.conn.query("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + + % Batch insert 2 rows + queries = { ... + "INSERT INTO batch_test VALUES (1,'Alice')", ... + "INSERT INTO batch_test VALUES (2,'Bob')" ... + }; + res = testCase.conn.queryBatch(queries); + + % Verify JDBC return codes + testCase.verifyEqual(res, [1 1]); + + % Verify table contents + T = testCase.conn.query("SELECT id, name FROM batch_test ORDER BY id"); + testCase.verifyEqual(T.id, [1; 2]); + testCase.verifyEqual(string(T.name), ["Alice"; "Bob"]); end end end From 7fd1846fa5f8a4ce1f14cc3ed81c199fba1d28a9 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Mon, 15 Sep 2025 04:19:44 +0200 Subject: [PATCH 23/44] Added rollback test to make sure the logic works fine if something fails during an N-Batch query. --- .../polyphenyconnector/QueryExecutorTest.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index 3a78642..fd645a0 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -2,7 +2,6 @@ import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; - import java.util.Arrays; import java.util.List; @@ -210,6 +209,8 @@ void testDeleteFromTable() { @Test void testBatchInsertEmployees() { + + // Insert the List of queries List queries = Arrays.asList( "INSERT INTO unittest_namespace.batch_table VALUES (1, 'Alice', 'F', DATE '1990-01-15', 1001)", "INSERT INTO unittest_namespace.batch_table VALUES (2, 'Bob', 'M', DATE '1989-05-12', 1002)", @@ -226,17 +227,43 @@ void testBatchInsertEmployees() { "INSERT INTO unittest_namespace.batch_table VALUES (13, 'Beya', 'F', DATE '1994-05-25', 1013)" ); + // Do the batch execution using executeBatch(...) int[] counts = myexecutor.executeBatch( "sql", queries ); + // Test that the lenghth of the counts vector is 13 (for 13 queries in the queries liest). assertEquals( 13, counts.length, "Batch should return 13 results" ); + + // Test the i-th entry in the counts vector is actually 1 (because the i-th query changed exactly 1 row) for ( int c : counts ) { assertEquals( 1, c, "Each INSERT should affect exactly 1 row" ); } + // Test the result has the correct type Object result = myexecutor.execute( "sql", "SELECT COUNT(*) FROM unittest_namespace.batch_table" ); assertTrue( result instanceof Long || result instanceof Integer ); + + // Test the rowcount is correct. int rowCount = ((Number) result).intValue(); assertEquals( 13, rowCount, "Table should contain 13 rows after batch insert" ); } + + @Test + void testBatchRollbackOnFailure() { + myexecutor.execute( "sql", "DELETE FROM unittest_namespace.batch_table" ); + + List queries = Arrays.asList( + "INSERT INTO unittest_namespace.batch_table VALUES (1, 'Alice')", + "Purposefully messed up query message to produce a failure" // duplicate PK + ); + + assertThrows( RuntimeException.class, () -> { + myexecutor.executeBatch( "sql", queries ); + } ); + + Object result = myexecutor.execute( "sql", "SELECt * FROM unittest_namespace.batch_table" ); + // Polypheny just reports 0 instead of throwing + assertNull( result ); + } + } From 8a28bf2e1f8fa7262293b96c3510e13b2c4d6b45 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Mon, 15 Sep 2025 04:22:51 +0200 Subject: [PATCH 24/44] Added some comments and cleaned up the tests a bit. --- .../test/java/polyphenyconnector/QueryExecutorTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index fd645a0..7bbbd26 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -250,19 +250,22 @@ void testBatchInsertEmployees() { @Test void testBatchRollbackOnFailure() { - myexecutor.execute( "sql", "DELETE FROM unittest_namespace.batch_table" ); + // Prepare one correct and one ill posed SQL statement to query as batch later. List queries = Arrays.asList( "INSERT INTO unittest_namespace.batch_table VALUES (1, 'Alice')", "Purposefully messed up query message to produce a failure" // duplicate PK ); + // Run the ill posed batch query and test an exception is thrown. assertThrows( RuntimeException.class, () -> { myexecutor.executeBatch( "sql", queries ); } ); + // Query the whole table to make sure it is really empty. Object result = myexecutor.execute( "sql", "SELECt * FROM unittest_namespace.batch_table" ); - // Polypheny just reports 0 instead of throwing + + // Test the query comes back as null i.e. the executeBatch has indeed been rolled back and the table is unchanged assertNull( result ); } From 747f1f23f6e19f21659ffa0f3b5db039fcbbbe98 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Tue, 16 Sep 2025 10:45:03 +0200 Subject: [PATCH 25/44] Refactored the Polypheny Wrapper, so that language is only an input parameter in query and queryBatch, not in the PolyWrapper. Now it mirrors the Java functionality. --- app/bin/main/Polypheny.m | 13 +++++----- app/bin/main/PolyphenyWrapperTest.m | 30 ++++++++++++------------ app/src/main/java/Polypheny.m | 13 +++++----- app/src/main/java/PolyphenyWrapperTest.m | 30 ++++++++++++------------ 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/app/bin/main/Polypheny.m b/app/bin/main/Polypheny.m index dd79b8c..f6619a1 100644 --- a/app/bin/main/Polypheny.m +++ b/app/bin/main/Polypheny.m @@ -2,7 +2,6 @@ % POLYPHENY MATLAB wrapper for the Polypheny Java connector. Wraps polyphenyconnector.PolyphenyConnection % and polyphenyconnector.QueryExecutor to run queries from MATLAB properties (Access = private) - language % 'sql' | 'mongoql' | 'cypher' polyConnection % Java PolyphenyConnection queryExecutor % Java QueryExecutor @@ -11,7 +10,7 @@ methods - function PolyWrapper = Polypheny( language, host, port, user, password ) + function PolyWrapper = Polypheny( host, port, user, password ) % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor % LANGUAGE: The database language ('sql', 'mongoql', 'cypher') % HOST: Database host (e.g. 'localhost') @@ -19,19 +18,19 @@ % USER: Username % PASSWORD: Password - PolyWrapper.language = language; PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); end - function T = query( PolyWrapper, queryStr ) + function T = query( PolyWrapper, language, queryStr ) % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java % POLYWRAPPER: The PolyWrapper Matlab object + % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher % QUERYSTR: The queryStr set by the user % @return T: The result of the query -> return type differs for SQL,MongoQl and Cyper - r = PolyWrapper.queryExecutor.execute(string(PolyWrapper.language), queryStr ); + r = PolyWrapper.queryExecutor.execute(string(language), queryStr ); if isempty( r ) T = []; @@ -50,7 +49,7 @@ end end - function result = queryBatch( PolyWrapper, queryList ) + function result = queryBatch( PolyWrapper, language, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) % @@ -64,7 +63,7 @@ for i = 1:numel(queryList) javaList.add( string(queryList{i}) ); end - java_result = PolyWrapper.queryExecutor.executeBatch( string(PolyWrapper.language), javaList ); + java_result = PolyWrapper.queryExecutor.executeBatch( string(language), javaList ); result = double(java_result(:))'; end diff --git a/app/bin/main/PolyphenyWrapperTest.m b/app/bin/main/PolyphenyWrapperTest.m index b44ffbd..4e0ccc2 100644 --- a/app/bin/main/PolyphenyWrapperTest.m +++ b/app/bin/main/PolyphenyWrapperTest.m @@ -6,8 +6,7 @@ methods (TestMethodSetup) function setupConnection(testCase) startup; - testCase.conn = polypheny.Polypheny( ... - 'sql', 'localhost', int32(20590), 'pa', '' ); + testCase.conn = polypheny.Polypheny('localhost', int32(20590), 'pa', '' ); end end @@ -19,16 +18,16 @@ function closeConnection(testCase) methods (Test) function testScalar(testCase) - r = testCase.conn.query("SELECT 1 AS x"); + r = testCase.conn.query( "sql" ,"SELECT 1 AS x"); testCase.verifyEqual(r, 1); end function testTable(testCase) - testCase.conn.query("DROP TABLE IF EXISTS wrapper_test"); - testCase.conn.query("CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); - testCase.conn.query("INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); + testCase.conn.query("sql" ,"DROP TABLE IF EXISTS wrapper_test"); + testCase.conn.query("sql" ,"CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("sql" ,"INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); - T = testCase.conn.query("SELECT * FROM wrapper_test ORDER BY id"); + T = testCase.conn.query("sql" ,"SELECT * FROM wrapper_test ORDER BY id"); if istable(T) % Expected: table output with column "name" @@ -42,29 +41,30 @@ function testTable(testCase) end function testEmpty(testCase) - T = testCase.conn.query("SELECT * FROM wrapper_test WHERE id=999"); + T = testCase.conn.query("sql" ,"SELECT * FROM wrapper_test WHERE id=999"); testCase.verifyEmpty(T); end - function testBatchInsert(testCase) + function testBatchInsert(testCase) % Prepare table - testCase.conn.query("DROP TABLE IF EXISTS batch_test"); - testCase.conn.query("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("sql" ,"DROP TABLE IF EXISTS batch_test"); + testCase.conn.query("sql" ,"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); % Batch insert 2 rows queries = { ... "INSERT INTO batch_test VALUES (1,'Alice')", ... "INSERT INTO batch_test VALUES (2,'Bob')" ... }; - res = testCase.conn.queryBatch(queries); + result = testCase.conn.queryBatch("sql" ,queries); % Verify JDBC return codes - testCase.verifyEqual(res, [1 1]); + testCase.verifyEqual(result, [1 1]); % Verify table contents - T = testCase.conn.query("SELECT id, name FROM batch_test ORDER BY id"); + T = testCase.conn.query("sql" ,"SELECT id, name FROM batch_test ORDER BY id"); testCase.verifyEqual(T.id, [1; 2]); testCase.verifyEqual(string(T.name), ["Alice"; "Bob"]); end + end -end +end \ No newline at end of file diff --git a/app/src/main/java/Polypheny.m b/app/src/main/java/Polypheny.m index dd79b8c..f6619a1 100644 --- a/app/src/main/java/Polypheny.m +++ b/app/src/main/java/Polypheny.m @@ -2,7 +2,6 @@ % POLYPHENY MATLAB wrapper for the Polypheny Java connector. Wraps polyphenyconnector.PolyphenyConnection % and polyphenyconnector.QueryExecutor to run queries from MATLAB properties (Access = private) - language % 'sql' | 'mongoql' | 'cypher' polyConnection % Java PolyphenyConnection queryExecutor % Java QueryExecutor @@ -11,7 +10,7 @@ methods - function PolyWrapper = Polypheny( language, host, port, user, password ) + function PolyWrapper = Polypheny( host, port, user, password ) % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor % LANGUAGE: The database language ('sql', 'mongoql', 'cypher') % HOST: Database host (e.g. 'localhost') @@ -19,19 +18,19 @@ % USER: Username % PASSWORD: Password - PolyWrapper.language = language; PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); end - function T = query( PolyWrapper, queryStr ) + function T = query( PolyWrapper, language, queryStr ) % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java % POLYWRAPPER: The PolyWrapper Matlab object + % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher % QUERYSTR: The queryStr set by the user % @return T: The result of the query -> return type differs for SQL,MongoQl and Cyper - r = PolyWrapper.queryExecutor.execute(string(PolyWrapper.language), queryStr ); + r = PolyWrapper.queryExecutor.execute(string(language), queryStr ); if isempty( r ) T = []; @@ -50,7 +49,7 @@ end end - function result = queryBatch( PolyWrapper, queryList ) + function result = queryBatch( PolyWrapper, language, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) % @@ -64,7 +63,7 @@ for i = 1:numel(queryList) javaList.add( string(queryList{i}) ); end - java_result = PolyWrapper.queryExecutor.executeBatch( string(PolyWrapper.language), javaList ); + java_result = PolyWrapper.queryExecutor.executeBatch( string(language), javaList ); result = double(java_result(:))'; end diff --git a/app/src/main/java/PolyphenyWrapperTest.m b/app/src/main/java/PolyphenyWrapperTest.m index b44ffbd..4e0ccc2 100644 --- a/app/src/main/java/PolyphenyWrapperTest.m +++ b/app/src/main/java/PolyphenyWrapperTest.m @@ -6,8 +6,7 @@ methods (TestMethodSetup) function setupConnection(testCase) startup; - testCase.conn = polypheny.Polypheny( ... - 'sql', 'localhost', int32(20590), 'pa', '' ); + testCase.conn = polypheny.Polypheny('localhost', int32(20590), 'pa', '' ); end end @@ -19,16 +18,16 @@ function closeConnection(testCase) methods (Test) function testScalar(testCase) - r = testCase.conn.query("SELECT 1 AS x"); + r = testCase.conn.query( "sql" ,"SELECT 1 AS x"); testCase.verifyEqual(r, 1); end function testTable(testCase) - testCase.conn.query("DROP TABLE IF EXISTS wrapper_test"); - testCase.conn.query("CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); - testCase.conn.query("INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); + testCase.conn.query("sql" ,"DROP TABLE IF EXISTS wrapper_test"); + testCase.conn.query("sql" ,"CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("sql" ,"INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); - T = testCase.conn.query("SELECT * FROM wrapper_test ORDER BY id"); + T = testCase.conn.query("sql" ,"SELECT * FROM wrapper_test ORDER BY id"); if istable(T) % Expected: table output with column "name" @@ -42,29 +41,30 @@ function testTable(testCase) end function testEmpty(testCase) - T = testCase.conn.query("SELECT * FROM wrapper_test WHERE id=999"); + T = testCase.conn.query("sql" ,"SELECT * FROM wrapper_test WHERE id=999"); testCase.verifyEmpty(T); end - function testBatchInsert(testCase) + function testBatchInsert(testCase) % Prepare table - testCase.conn.query("DROP TABLE IF EXISTS batch_test"); - testCase.conn.query("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("sql" ,"DROP TABLE IF EXISTS batch_test"); + testCase.conn.query("sql" ,"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); % Batch insert 2 rows queries = { ... "INSERT INTO batch_test VALUES (1,'Alice')", ... "INSERT INTO batch_test VALUES (2,'Bob')" ... }; - res = testCase.conn.queryBatch(queries); + result = testCase.conn.queryBatch("sql" ,queries); % Verify JDBC return codes - testCase.verifyEqual(res, [1 1]); + testCase.verifyEqual(result, [1 1]); % Verify table contents - T = testCase.conn.query("SELECT id, name FROM batch_test ORDER BY id"); + T = testCase.conn.query("sql" ,"SELECT id, name FROM batch_test ORDER BY id"); testCase.verifyEqual(T.id, [1; 2]); testCase.verifyEqual(string(T.name), ["Alice"; "Bob"]); end + end -end +end \ No newline at end of file From 8cccf8d3e734e25c4f2e4f5e26a216cd98629a68 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Tue, 16 Sep 2025 13:01:13 +0200 Subject: [PATCH 26/44] Fixed the issues with 'Unsupported language' vs 'Language not yet implemented' when giving language = 'nonsense' vs language = sql, mongoql, cypher. --- app/src/main/java/polyphenyconnector/QueryExecutor.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 0669c56..57c277e 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -36,8 +36,7 @@ public Object execute( String language, String query ) { polyconnection.openIfNeeded(); switch ( language.toLowerCase() ) { default: - System.err.println( "Unsupported language: " + language ); - return null; + throw new UnsupportedOperationException( "Unsupported language: " + language ); case "sql": try ( Statement stmt = polyconnection.getConnection().createStatement() ) { @@ -80,7 +79,7 @@ public int[] executeBatch( String language, List query_list ) { switch ( language.toLowerCase() ) { default: - System.err.println( "Unsupported language: " + language ); + throw new UnsupportedOperationException( "Unsupported language: " + language ); case "sql": try { From 26f8be6d827a45b3eb41fe291cc2c743e279ebf3 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Wed, 17 Sep 2025 16:23:35 +0200 Subject: [PATCH 27/44] Fixed the startup.m issue by just calling the file once in the beginning before the PolyphenyConnection.java object is created when calling the PolyWrapper.m. That way theres no ambiguity or depenendence on any Matlab specific Toolbox features (like automatic install options). Performance is not affected by this since the PolyWrapper object is only called once to establish the connection through the PolyphenyConnection.java class that it mirrors in Matlab. --- app/bin/main/Polypheny.m | 31 +++++++++++----- app/src/main/java/Polypheny.m | 31 +++++++++++----- .../PolyphenyConnectionTestHelper.java | 7 ++++ .../polyphenyconnector/QueryExecutorTest.java | 35 ++++++++++++++++++- 4 files changed, 85 insertions(+), 19 deletions(-) diff --git a/app/bin/main/Polypheny.m b/app/bin/main/Polypheny.m index f6619a1..dc41671 100644 --- a/app/bin/main/Polypheny.m +++ b/app/bin/main/Polypheny.m @@ -8,7 +8,6 @@ end methods - function PolyWrapper = Polypheny( host, port, user, password ) % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor @@ -18,6 +17,10 @@ % USER: Username % PASSWORD: Password + % This makes sure that Matlab sees Java classes supplied by the .jar files in the Matlabtoolbox PolyphenyConnector.mtlbx + if ~polypheny.Polypheny.hasPolypheny() + startup(); + end PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); end @@ -30,18 +33,18 @@ % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher % QUERYSTR: The queryStr set by the user % @return T: The result of the query -> return type differs for SQL,MongoQl and Cyper - r = PolyWrapper.queryExecutor.execute(string(language), queryStr ); + java_result = PolyWrapper.queryExecutor.execute(string(language), queryStr ); - if isempty( r ) + if isempty( java_result ) T = []; - elseif isscalar( r ) - T = r; + elseif isscalar( java_result ) + T = java_result; - elseif isa(r,'java.lang.Object[]') && numel(r) == 2 - r = cell(r); - colNames = cell(r{1}); - data = cell(r{2}); % Object[][] → MATLAB cell + elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 + result = cell(java_result); + colNames = cell(result{1}); + data = cell(result{2}); % Object[][] → MATLAB cell T = cell2table(data, 'VariableNames', colNames); else @@ -73,4 +76,14 @@ function close( PolyWrapper ) PolyWrapper.polyConnection.close(); end end + + methods (Static) + function flag = hasPolypheny() + % HASPOLYPHENY Returns true if Polypheny Java classes are available because the exist('polyphenyconnector.PolyphenyConnection','class') + % returns 8 if Matlab sees the Java class and 0 otherwise. + flag = ( exist('polyphenyconnector.PolyphenyConnection','class') == 8 ); + end + + end + end diff --git a/app/src/main/java/Polypheny.m b/app/src/main/java/Polypheny.m index f6619a1..dc41671 100644 --- a/app/src/main/java/Polypheny.m +++ b/app/src/main/java/Polypheny.m @@ -8,7 +8,6 @@ end methods - function PolyWrapper = Polypheny( host, port, user, password ) % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor @@ -18,6 +17,10 @@ % USER: Username % PASSWORD: Password + % This makes sure that Matlab sees Java classes supplied by the .jar files in the Matlabtoolbox PolyphenyConnector.mtlbx + if ~polypheny.Polypheny.hasPolypheny() + startup(); + end PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); end @@ -30,18 +33,18 @@ % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher % QUERYSTR: The queryStr set by the user % @return T: The result of the query -> return type differs for SQL,MongoQl and Cyper - r = PolyWrapper.queryExecutor.execute(string(language), queryStr ); + java_result = PolyWrapper.queryExecutor.execute(string(language), queryStr ); - if isempty( r ) + if isempty( java_result ) T = []; - elseif isscalar( r ) - T = r; + elseif isscalar( java_result ) + T = java_result; - elseif isa(r,'java.lang.Object[]') && numel(r) == 2 - r = cell(r); - colNames = cell(r{1}); - data = cell(r{2}); % Object[][] → MATLAB cell + elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 + result = cell(java_result); + colNames = cell(result{1}); + data = cell(result{2}); % Object[][] → MATLAB cell T = cell2table(data, 'VariableNames', colNames); else @@ -73,4 +76,14 @@ function close( PolyWrapper ) PolyWrapper.polyConnection.close(); end end + + methods (Static) + function flag = hasPolypheny() + % HASPOLYPHENY Returns true if Polypheny Java classes are available because the exist('polyphenyconnector.PolyphenyConnection','class') + % returns 8 if Matlab sees the Java class and 0 otherwise. + flag = ( exist('polyphenyconnector.PolyphenyConnection','class') == 8 ); + end + + end + end diff --git a/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java b/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java index 3c6cbce..adcab13 100644 --- a/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java +++ b/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java @@ -1,6 +1,7 @@ package polyphenyconnector; import java.sql.DriverManager; +import java.sql.SQLException; public class PolyphenyConnectionTestHelper { @@ -31,4 +32,10 @@ static void waitForPolypheny() throws Exception { } } + + public static void ensurePostgresAdapter( PolyphenyConnection conn ) throws SQLException { + QueryExecutor exec = new QueryExecutor( conn ); + exec.execute( "sql", "CREATE ADAPTER IF NOT EXISTS postgresql1 USING postgresql ..." ); + } + } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index 7bbbd26..9af9e37 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -25,7 +25,7 @@ static void setUpNamespaceAndTable() throws Exception { // cleanly, in case tests break mid run the cleanup "@Afterall" might not have been executed properly. try { myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); - myexecutor.execute( "sql", "DROP NAMESPACE unittest_namespace" ); + myexecutor.execute( "sql", "DROP NAMESPACE IF EXISTS unittest_namespace" ); } catch ( Exception ignored ) { } @@ -269,4 +269,37 @@ void testBatchRollbackOnFailure() { assertNull( result ); } + + @Test + void testQueryWithSpacesPostgreSQL() { + myexecutor.execute( "sql", "CREATE TABLE unittest_namespace.pg_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY (id)) ON STORE postgres" ); + myexecutor.execute( "sql", "ALTER TABLE unittest_namespace.unittest_table ADD PLACEMENT ON postgres_adapter_name;" ); + + // Insert id = 1,2 and name = Alice, Bob into the table. + + myexecutor.execute( "sql", " INSERT INTO unittest_namespace.unittest_PostgresTable VALUES (1, 'Alice') USING PostgreSQL" ); + myexecutor.execute( "sql", " INSERT INTO unittest_namespace.unittest_PostgresTable VALUES (2, 'Bob')" ); + + // Query the result from the table. + Object result = myexecutor.execute( "sql", "SELECT id, name FROM unittest_namespace.unittest_PostgresTable ORDER BY id" ); + + // Check the contents of the query are correct. + Object[] arr = (Object[]) result; + String[] colNames = (String[]) arr[0]; + Object[][] data = (Object[][]) arr[1]; + + // Test the column names match. + assertArrayEquals( new String[]{ "id", "name" }, colNames ); + + // Test the array has indeed length 2 (2 rows for Alice and Bob) + assertEquals( 2, data.length ); + + // Test the contents of each row are correct. + assertArrayEquals( new Object[]{ 1, "Alice" }, data[0] ); + assertArrayEquals( new Object[]{ 2, "Bob" }, data[1] ); + + myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_PostgresTable" ); + + } + } From 837dfe92a3bad920aa44e5e8ed016c0e95cb98b0 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Thu, 18 Sep 2025 02:06:44 +0200 Subject: [PATCH 28/44] Removed the PostgreSQL test from the JUnit tests. --- .gitignore | 2 ++ .../polyphenyconnector/QueryExecutorTest.java | 33 ------------------- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 45aca94..5e22b48 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ app\build\libs # ignore the matlab add-on folder structure matlab-polypheny-connector + +/matlab-polypheny-connector/Toolbox1/ \ No newline at end of file diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index 9af9e37..30c35bb 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -269,37 +269,4 @@ void testBatchRollbackOnFailure() { assertNull( result ); } - - @Test - void testQueryWithSpacesPostgreSQL() { - myexecutor.execute( "sql", "CREATE TABLE unittest_namespace.pg_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY (id)) ON STORE postgres" ); - myexecutor.execute( "sql", "ALTER TABLE unittest_namespace.unittest_table ADD PLACEMENT ON postgres_adapter_name;" ); - - // Insert id = 1,2 and name = Alice, Bob into the table. - - myexecutor.execute( "sql", " INSERT INTO unittest_namespace.unittest_PostgresTable VALUES (1, 'Alice') USING PostgreSQL" ); - myexecutor.execute( "sql", " INSERT INTO unittest_namespace.unittest_PostgresTable VALUES (2, 'Bob')" ); - - // Query the result from the table. - Object result = myexecutor.execute( "sql", "SELECT id, name FROM unittest_namespace.unittest_PostgresTable ORDER BY id" ); - - // Check the contents of the query are correct. - Object[] arr = (Object[]) result; - String[] colNames = (String[]) arr[0]; - Object[][] data = (Object[][]) arr[1]; - - // Test the column names match. - assertArrayEquals( new String[]{ "id", "name" }, colNames ); - - // Test the array has indeed length 2 (2 rows for Alice and Bob) - assertEquals( 2, data.length ); - - // Test the contents of each row are correct. - assertArrayEquals( new Object[]{ 1, "Alice" }, data[0] ); - assertArrayEquals( new Object[]{ 2, "Bob" }, data[1] ); - - myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_PostgresTable" ); - - } - } From 95b6857773dfd024c2f2561e384d6ca74345a809 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 20 Sep 2025 04:22:12 +0200 Subject: [PATCH 29/44] Added mongoql to execute and adapted wrapper --- app/bin/main/Polypheny.m | 55 ++++++++++---- app/src/main/java/Polypheny.m | 55 ++++++++++---- .../polyphenyconnector/QueryExecutor.java | 76 +++++++++++++++++-- .../polyphenyconnector/QueryExecutorTest.java | 37 +++++++++ 4 files changed, 185 insertions(+), 38 deletions(-) diff --git a/app/bin/main/Polypheny.m b/app/bin/main/Polypheny.m index dc41671..41ff7c7 100644 --- a/app/bin/main/Polypheny.m +++ b/app/bin/main/Polypheny.m @@ -27,32 +27,55 @@ - function T = query( PolyWrapper, language, queryStr ) + function matlab_result = query( PolyWrapper, language, queryStr ) % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java % POLYWRAPPER: The PolyWrapper Matlab object % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher % QUERYSTR: The queryStr set by the user - % @return T: The result of the query -> return type differs for SQL,MongoQl and Cyper + % @return T: The result of the query -> return type differs for SQL,MongoQl and Cypher java_result = PolyWrapper.queryExecutor.execute(string(language), queryStr ); - - if isempty( java_result ) - T = []; - elseif isscalar( java_result ) - T = java_result; + % SQL case + if language == "sql" + if isempty( java_result ) + matlab_result = []; + + elseif isscalar( java_result ) + matlab_result = java_result; + + elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 + matlab_result = cell(java_result); + colNames = cell(matlab_result{1}); + data = cell(matlab_result{2}); % Object[][] → MATLAB cell + matlab_result = cell2table(data, 'VariableNames', colNames); + + else + matlab_result = []; % fallback to avoid cell2table crash + end - elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 - result = cell(java_result); - colNames = cell(result{1}); - data = cell(result{2}); % Object[][] → MATLAB cell - T = cell2table(data, 'VariableNames', colNames); + % MongoQL case + elseif language == "mongoql" + + if isscalar( java_result) + matlab_result = java_result; + + elseif ischar( java_result ) || isstring (java_result) + matlab_result = string(java_result); + + else + try + matlab_result = java_result + catch ME %Matlab Exception + disp("Error: " + ME.message) + end + + end - else - T = []; % fallback to avoid cell2table crash end + end - function result = queryBatch( PolyWrapper, language, queryList ) + function matlab_result = queryBatch( PolyWrapper, language, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) % @@ -67,7 +90,7 @@ javaList.add( string(queryList{i}) ); end java_result = PolyWrapper.queryExecutor.executeBatch( string(language), javaList ); - result = double(java_result(:))'; + matlab_result = double(java_result(:))'; end function close( PolyWrapper ) diff --git a/app/src/main/java/Polypheny.m b/app/src/main/java/Polypheny.m index dc41671..41ff7c7 100644 --- a/app/src/main/java/Polypheny.m +++ b/app/src/main/java/Polypheny.m @@ -27,32 +27,55 @@ - function T = query( PolyWrapper, language, queryStr ) + function matlab_result = query( PolyWrapper, language, queryStr ) % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java % POLYWRAPPER: The PolyWrapper Matlab object % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher % QUERYSTR: The queryStr set by the user - % @return T: The result of the query -> return type differs for SQL,MongoQl and Cyper + % @return T: The result of the query -> return type differs for SQL,MongoQl and Cypher java_result = PolyWrapper.queryExecutor.execute(string(language), queryStr ); - - if isempty( java_result ) - T = []; - elseif isscalar( java_result ) - T = java_result; + % SQL case + if language == "sql" + if isempty( java_result ) + matlab_result = []; + + elseif isscalar( java_result ) + matlab_result = java_result; + + elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 + matlab_result = cell(java_result); + colNames = cell(matlab_result{1}); + data = cell(matlab_result{2}); % Object[][] → MATLAB cell + matlab_result = cell2table(data, 'VariableNames', colNames); + + else + matlab_result = []; % fallback to avoid cell2table crash + end - elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 - result = cell(java_result); - colNames = cell(result{1}); - data = cell(result{2}); % Object[][] → MATLAB cell - T = cell2table(data, 'VariableNames', colNames); + % MongoQL case + elseif language == "mongoql" + + if isscalar( java_result) + matlab_result = java_result; + + elseif ischar( java_result ) || isstring (java_result) + matlab_result = string(java_result); + + else + try + matlab_result = java_result + catch ME %Matlab Exception + disp("Error: " + ME.message) + end + + end - else - T = []; % fallback to avoid cell2table crash end + end - function result = queryBatch( PolyWrapper, language, queryList ) + function matlab_result = queryBatch( PolyWrapper, language, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) % @@ -67,7 +90,7 @@ javaList.add( string(queryList{i}) ); end java_result = PolyWrapper.queryExecutor.executeBatch( string(language), javaList ); - result = double(java_result(:))'; + matlab_result = double(java_result(:))'; end function close( PolyWrapper ) diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 57c277e..50a9504 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -40,26 +40,65 @@ public Object execute( String language, String query ) { case "sql": try ( Statement stmt = polyconnection.getConnection().createStatement() ) { + String first = query.trim().toUpperCase(); + // SELECT statements if ( first.startsWith( "SELECT" ) ) { try ( ResultSet rs = stmt.executeQuery( query ) ) { - return ResultToMatlab( rs ); + return SQLResultToMatlab( rs ); } + + // INSERT, UPDATE, DELETE, CREATE, DROP, ... statements } else { - stmt.executeUpdate( query ); // INSERT, UPDATE, DELETE, CREATE, DROP, ... + stmt.executeUpdate( query ); return null; + } + + } catch ( SQLException e ) { + throw translateSQLException( e ); } catch ( Exception e ) { throw new RuntimeException( "SQL execution failed: " + e.getMessage(), e ); } case "mongoql": - throw new UnsupportedOperationException( "MongoQL execution not yet implemented." ); + try ( Statement stmt = polyconnection.getConnection().createStatement(); ResultSet rs = stmt.executeQuery( query ) ) { + + List rows = new ArrayList<>(); + ResultSetMetaData md = rs.getMetaData(); + int cols = md.getColumnCount(); + + while ( rs.next() ) { + if ( cols == 1 ) { + rows.add( rs.getObject( 1 ) ); + } else { + Object[] row = new Object[cols]; + for ( int i = 0; i < cols; i++ ) { + row[i] = rs.getObject( i + 1 ); + } + rows.add( row ); + } + } + + if ( rows.isEmpty() ) { + return null; // empty query we return null + } else if ( rows.size() == 1 && cols == 1 ) { + return rows.get( 0 ); // single scalar or single String + } else if ( cols == 1 ) { + return rows.toArray( new String[0] ); // multiple JSON docs (i.e. a collection) + } else { + return rows.toArray( new Object[0] ); // fallback + } + + } catch ( Exception e ) { + throw new RuntimeException( "MongoQL execution failed: " + e.getMessage(), e ); + } case "cypher": throw new UnsupportedOperationException( "Cypher execution not yet implemented." ); } + } @@ -95,10 +134,14 @@ public int[] executeBatch( String language, List query_list ) { int[] result = stmt.executeBatch(); polyconnection.commitTransaction(); return result; + + } catch ( SQLException e ) { + throw translateSQLException( e ); } catch ( Exception e ) { polyconnection.rollbackTransaction(); throw new RuntimeException( "SQL batch execution failed. Transaction was rolled back: " + e.getMessage(), e ); } + } catch ( SQLException e ) { throw new RuntimeException( "Failed to manage transaction", e ); } @@ -122,7 +165,7 @@ public int[] executeBatch( String language, List query_list ) { * * @return: Result from the query which is either null/scalar/table (SQL), TODO document (MongoQL, or (Cypher) **/ - public Object ResultToMatlab( ResultSet rs ) throws Exception { + public Object SQLResultToMatlab( ResultSet rs ) throws Exception { ResultSetMetaData meta = rs.getMetaData(); int colCount = meta.getColumnCount(); @@ -163,10 +206,8 @@ public Object ResultToMatlab( ResultSet rs ) throws Exception { row[i] = rs.getObject( i + 1 ); // Saves each entry } rows.add( row ); // Append row to the List - // System.err.println( "Fetched row: " + java.util.Arrays.toString( row ) ); } while ( rs.next() ); // First row already fetched above with rs.next() so we use do while - // System.err.println( "Rows: " + java.util.Arrays.deepToString( rows.toArray() ) ); // Ensure that the colNames and rows have the same number of columns if ( colNames.length != rows.get( 0 ).length ) { throw new RuntimeException( "Mismatch: colNames and rowData column count don't match" ); @@ -177,4 +218,27 @@ public Object ResultToMatlab( ResultSet rs ) throws Exception { return new Object[]{ colNames, ResultArray }; } + + /** + * @Description + * This method ensures that exceptions thrown by Polypheny (and propagated through the JDBC driver) due to user fault when calling + * execute or executeBatch are translated in a user-interpretable error to be propagated to Matlab, instead of just failing + * with an obscure error/exception. + * + * @param e The exception caught. 08 denotes an error with the Polypheny connetion, 42 denotes a + * @return + */ + private RuntimeException translateSQLException( SQLException e ) { + String state = e.getSQLState(); + if ( state != null ) { + if ( state.startsWith( "08" ) ) { + return new RuntimeException( "Connection error: " + e.getMessage(), e ); + } + if ( state.startsWith( "42" ) ) { + return new RuntimeException( "Syntax error in query: " + e.getMessage(), e ); + } + } + return new RuntimeException( "SQL execution failed: " + e.getMessage(), e ); + } + } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index 30c35bb..d9c3f21 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -269,4 +269,41 @@ void testBatchRollbackOnFailure() { assertNull( result ); } + + @Test + void testConnectionFailure() { + assertThrows( RuntimeException.class, () -> { + PolyphenyConnection badConn = new PolyphenyConnection( "localhost", 9999, "pa", "" ); + QueryExecutor badExec = new QueryExecutor( badConn ); + badExec.execute( "sql", "SELECT 1" ); // should fail to connect + } ); + } + + + @Test + void testSyntaxError() { + RuntimeException ex = assertThrows( RuntimeException.class, () -> { + myexecutor.execute( "sql", "SELEC WRONG FROM nowhere" ); // typo: SELEC + } ); + assertTrue( ex.getMessage().contains( "Syntax error" ) || + ex.getMessage().contains( "execution failed" ), + "Exception message should indicate syntax error" ); + } + + + @Test + void testCommitFailureRollback() { + List queries = Arrays.asList( + "INSERT INTO unittest_namespace.batch_table VALUES (1, 'Alice', 'F', DATE '1990-01-15', 1001)", + "Intentional nonsense to produce a failure" // PK violation + ); + + assertThrows( RuntimeException.class, () -> { + myexecutor.executeBatch( "sql", queries ); + } ); + + Object result = myexecutor.execute( "sql", "SELECT * FROM unittest_namespace.batch_table" ); + assertNull( result, "Batch should have rolled back and left the table empty" ); + } + } From 6d02ebb2db587136ed5a922379c1bd073eba2602 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 20 Sep 2025 16:40:14 +0200 Subject: [PATCH 30/44] Made variable names consistent. Changed returntype in execute() to return the number of changed rows for INSERT (and the likes) statements in SQL. Added one test to test the functionality. --- .../polyphenyconnector/QueryExecutor.java | 41 +++++++++---------- .../polyphenyconnector/QueryExecutorTest.java | 9 ++++ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 50a9504..671da20 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -51,8 +51,8 @@ public Object execute( String language, String query ) { // INSERT, UPDATE, DELETE, CREATE, DROP, ... statements } else { - stmt.executeUpdate( query ); - return null; + int rs = stmt.executeUpdate( query ); + return rs; } @@ -65,30 +65,30 @@ public Object execute( String language, String query ) { case "mongoql": try ( Statement stmt = polyconnection.getConnection().createStatement(); ResultSet rs = stmt.executeQuery( query ) ) { - List rows = new ArrayList<>(); - ResultSetMetaData md = rs.getMetaData(); - int cols = md.getColumnCount(); + List Result = new ArrayList<>(); + ResultSetMetaData meta = rs.getMetaData(); + int colCount = meta.getColumnCount(); while ( rs.next() ) { - if ( cols == 1 ) { - rows.add( rs.getObject( 1 ) ); + if ( colCount == 1 ) { + Result.add( rs.getObject( 1 ) ); } else { - Object[] row = new Object[cols]; - for ( int i = 0; i < cols; i++ ) { + Object[] row = new Object[colCount]; + for ( int i = 0; i < colCount; i++ ) { row[i] = rs.getObject( i + 1 ); } - rows.add( row ); + Result.add( row ); } } - if ( rows.isEmpty() ) { + if ( Result.isEmpty() ) { return null; // empty query we return null - } else if ( rows.size() == 1 && cols == 1 ) { - return rows.get( 0 ); // single scalar or single String - } else if ( cols == 1 ) { - return rows.toArray( new String[0] ); // multiple JSON docs (i.e. a collection) + } else if ( Result.size() == 1 && colCount == 1 ) { + return Result.get( 0 ); // single scalar or single String + } else if ( colCount == 1 ) { + return Result.toArray( new String[0] ); // multiple JSON docs (i.e. a collection) } else { - return rows.toArray( new Object[0] ); // fallback + return Result.toArray( new Object[0] ); // fallback } } catch ( Exception e ) { @@ -158,18 +158,16 @@ public int[] executeBatch( String language, List query_list ) { /** * @Description - * - Casts the result of the queries to MatlabObjects, depending on - * the Database language (SQL, MongoQL, Cypher) + * - Casts the result of SQL queries to MatlabObjects * * @param rs: The result object of the query of type ResultSet * - * @return: Result from the query which is either null/scalar/table (SQL), TODO document (MongoQL, or (Cypher) + * @return: Result from the query which is either null/scalar/table **/ public Object SQLResultToMatlab( ResultSet rs ) throws Exception { ResultSetMetaData meta = rs.getMetaData(); int colCount = meta.getColumnCount(); - Object Result; Object[][] ResultArray; // ───────────────────────────── @@ -177,8 +175,7 @@ public Object SQLResultToMatlab( ResultSet rs ) throws Exception { // ───────────────────────────── if ( !rs.next() ) { System.out.println( "Empty result set." ); - Result = null; - return Result; + return null; } // ───────────────────────────── diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index d9c3f21..8df5168 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -107,6 +107,15 @@ void testTableLiteral() { // ───────────────────────────── + @Test + void testInsert() { + // Insert id = 1 and name = Alice into the table. + Object result = myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); + assertTrue( result instanceof Integer, "Expected an integer." ); + assertEquals( result, 1, "result should equal 1." ); + } + + @Test void testInsertAndSelect() { // Insert id = 1 and name = Alice into the table. From 97ba2ad4a96b221bfc7e4f28445cdbb0619f2f58 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 20 Sep 2025 19:29:24 +0200 Subject: [PATCH 31/44] Refactored the code so that it now supports a namespace argument in execute(language, namespace, query) in the matlabfunction. This is necessary so that MongoQL queries can be executed properly. --- app/bin/main/Polypheny.m | 8 +- app/bin/main/PolyphenyWrapperTest.m | 12 +-- app/src/main/java/Main.java | 2 +- app/src/main/java/Polypheny.m | 8 +- app/src/main/java/PolyphenyWrapperTest.m | 12 +-- app/src/main/java/QuickTest.java | 8 +- .../polyphenyconnector/QueryExecutor.java | 27 ++++++- .../PolyphenyConnectionTestHelper.java | 6 +- .../polyphenyconnector/QueryExecutorTest.java | 77 ++++++++++--------- 9 files changed, 90 insertions(+), 70 deletions(-) diff --git a/app/bin/main/Polypheny.m b/app/bin/main/Polypheny.m index 41ff7c7..22a0bed 100644 --- a/app/bin/main/Polypheny.m +++ b/app/bin/main/Polypheny.m @@ -27,13 +27,13 @@ - function matlab_result = query( PolyWrapper, language, queryStr ) + function matlab_result = query( PolyWrapper, language, namespace, queryStr ) % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java % POLYWRAPPER: The PolyWrapper Matlab object % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher % QUERYSTR: The queryStr set by the user % @return T: The result of the query -> return type differs for SQL,MongoQl and Cypher - java_result = PolyWrapper.queryExecutor.execute(string(language), queryStr ); + java_result = PolyWrapper.queryExecutor.execute(string(language), string(namespace), queryStr ); % SQL case if language == "sql" @@ -75,7 +75,7 @@ end - function matlab_result = queryBatch( PolyWrapper, language, queryList ) + function matlab_result = queryBatch( PolyWrapper, language, namespace, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) % @@ -89,7 +89,7 @@ for i = 1:numel(queryList) javaList.add( string(queryList{i}) ); end - java_result = PolyWrapper.queryExecutor.executeBatch( string(language), javaList ); + java_result = PolyWrapper.queryExecutor.executeBatch( string(language), string(namespace), javaList ); matlab_result = double(java_result(:))'; end diff --git a/app/bin/main/PolyphenyWrapperTest.m b/app/bin/main/PolyphenyWrapperTest.m index 4e0ccc2..7aa372d 100644 --- a/app/bin/main/PolyphenyWrapperTest.m +++ b/app/bin/main/PolyphenyWrapperTest.m @@ -18,16 +18,16 @@ function closeConnection(testCase) methods (Test) function testScalar(testCase) - r = testCase.conn.query( "sql" ,"SELECT 1 AS x"); + r = testCase.conn.query( "sql" , "" , "SELECT 1 AS x"); testCase.verifyEqual(r, 1); end function testTable(testCase) - testCase.conn.query("sql" ,"DROP TABLE IF EXISTS wrapper_test"); - testCase.conn.query("sql" ,"CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); - testCase.conn.query("sql" ,"INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); + testCase.conn.query("sql" , "" , "DROP TABLE IF EXISTS wrapper_test"); + testCase.conn.query("sql" , "" , "CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("sql" , "" , "INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); - T = testCase.conn.query("sql" ,"SELECT * FROM wrapper_test ORDER BY id"); + T = testCase.conn.query("sql" , "" , "SELECT * FROM wrapper_test ORDER BY id"); if istable(T) % Expected: table output with column "name" @@ -41,7 +41,7 @@ function testTable(testCase) end function testEmpty(testCase) - T = testCase.conn.query("sql" ,"SELECT * FROM wrapper_test WHERE id=999"); + T = testCase.conn.query("sql" , "" , "SELECT * FROM wrapper_test WHERE id=999"); testCase.verifyEmpty(T); end diff --git a/app/src/main/java/Main.java b/app/src/main/java/Main.java index 097592e..f7bb97e 100644 --- a/app/src/main/java/Main.java +++ b/app/src/main/java/Main.java @@ -12,7 +12,7 @@ public static void main( String[] args ) { PolyphenyConnection conn = new PolyphenyConnection( host, port, user, pass ); QueryExecutor executor = new QueryExecutor( conn ); - executor.execute( "sql", "SELECT * FROM emps;" ); + executor.execute( "sql", "emps", "SELECT * FROM emps;" ); conn.close(); } catch ( Exception e ) { e.printStackTrace(); diff --git a/app/src/main/java/Polypheny.m b/app/src/main/java/Polypheny.m index 41ff7c7..22a0bed 100644 --- a/app/src/main/java/Polypheny.m +++ b/app/src/main/java/Polypheny.m @@ -27,13 +27,13 @@ - function matlab_result = query( PolyWrapper, language, queryStr ) + function matlab_result = query( PolyWrapper, language, namespace, queryStr ) % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java % POLYWRAPPER: The PolyWrapper Matlab object % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher % QUERYSTR: The queryStr set by the user % @return T: The result of the query -> return type differs for SQL,MongoQl and Cypher - java_result = PolyWrapper.queryExecutor.execute(string(language), queryStr ); + java_result = PolyWrapper.queryExecutor.execute(string(language), string(namespace), queryStr ); % SQL case if language == "sql" @@ -75,7 +75,7 @@ end - function matlab_result = queryBatch( PolyWrapper, language, queryList ) + function matlab_result = queryBatch( PolyWrapper, language, namespace, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) % @@ -89,7 +89,7 @@ for i = 1:numel(queryList) javaList.add( string(queryList{i}) ); end - java_result = PolyWrapper.queryExecutor.executeBatch( string(language), javaList ); + java_result = PolyWrapper.queryExecutor.executeBatch( string(language), string(namespace), javaList ); matlab_result = double(java_result(:))'; end diff --git a/app/src/main/java/PolyphenyWrapperTest.m b/app/src/main/java/PolyphenyWrapperTest.m index 4e0ccc2..7aa372d 100644 --- a/app/src/main/java/PolyphenyWrapperTest.m +++ b/app/src/main/java/PolyphenyWrapperTest.m @@ -18,16 +18,16 @@ function closeConnection(testCase) methods (Test) function testScalar(testCase) - r = testCase.conn.query( "sql" ,"SELECT 1 AS x"); + r = testCase.conn.query( "sql" , "" , "SELECT 1 AS x"); testCase.verifyEqual(r, 1); end function testTable(testCase) - testCase.conn.query("sql" ,"DROP TABLE IF EXISTS wrapper_test"); - testCase.conn.query("sql" ,"CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); - testCase.conn.query("sql" ,"INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); + testCase.conn.query("sql" , "" , "DROP TABLE IF EXISTS wrapper_test"); + testCase.conn.query("sql" , "" , "CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("sql" , "" , "INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); - T = testCase.conn.query("sql" ,"SELECT * FROM wrapper_test ORDER BY id"); + T = testCase.conn.query("sql" , "" , "SELECT * FROM wrapper_test ORDER BY id"); if istable(T) % Expected: table output with column "name" @@ -41,7 +41,7 @@ function testTable(testCase) end function testEmpty(testCase) - T = testCase.conn.query("sql" ,"SELECT * FROM wrapper_test WHERE id=999"); + T = testCase.conn.query("sql" , "" , "SELECT * FROM wrapper_test WHERE id=999"); testCase.verifyEmpty(T); end diff --git a/app/src/main/java/QuickTest.java b/app/src/main/java/QuickTest.java index ee9810d..a381a6d 100644 --- a/app/src/main/java/QuickTest.java +++ b/app/src/main/java/QuickTest.java @@ -11,20 +11,20 @@ public static void main( String[] args ) throws Exception { QueryExecutor exec = new QueryExecutor( conn ); // 1) Scalar smoke test - Object r1 = exec.execute( "sql", "SELECT 1 AS x" ); + Object r1 = exec.execute( "sql", "", "SELECT 1 AS x" ); System.out.println( "Scalar result: " + r1 ); // 2) Table smoke test - Object r2 = exec.execute( "sql", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); + Object r2 = exec.execute( "sql", "", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); printTable( r2 ); // 3) First row from emps (1-row table) - Object r3 = exec.execute( "sql", "SELECT * FROM emps LIMIT 1" ); + Object r3 = exec.execute( "sql", "emps", "SELECT * FROM emps LIMIT 1" ); System.out.println( "First row from emps:" ); printTable( r3 ); // 4) Scalar from emps - Object r4 = exec.execute( "sql", "SELECT empid FROM emps LIMIT 1" ); + Object r4 = exec.execute( "sql", "emps", "SELECT empid FROM emps LIMIT 1" ); System.out.println( "First empid (scalar): " + r4 ); } finally { diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 671da20..04d7022 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -27,12 +27,15 @@ public QueryExecutor( PolyphenyConnection polyconnection ) { * - Executes the query depending on the language given by the user * * @param language: The database language that is used (e.g. SQL, MongoQL,Cypher) + * @param namespace: The namespace in the query string. For SQL this argument as no effect as we use JDBC's + * executeQuery(...)/executeUpdate(...). For MQL this argument will be passed to JDBC's execute(...) function. For further + * information consult Polpyheny's web documentation. * @param query: The query-text to be executed (e.g. FROM emps SELECT *) * * @return: ResultToMatlab(rs) which is a Matlab compatible object that is cast to the Matlab user. **/ - public Object execute( String language, String query ) { - + public Object execute( String language, String namespace, String query ) { + checkNamespace( language, namespace ); polyconnection.openIfNeeded(); switch ( language.toLowerCase() ) { default: @@ -108,12 +111,15 @@ public Object execute( String language, String query ) { * except SELECT are supported. For further information consult the Polyphenys JDBC Driver documentation. * * @param language The database language that is used (e.g. SQL, MongoQL,Cypher) + * @param namespace: The namespace in the query string. For SQL this argument as no effect as we use JDBC's + * executeQuery(...)/executeUpdate(...). For MQL this argument will be passed to JDBC's execute(...) function. For further + * information consult Polpyheny's web documentation. * @param query_list The list of query strings to be executed. * @return int[] result An array of integers, where the i-th entry will denote for the i-th query how many rows were touched, e.g. * n: n rows were updated, 0: no rows were touched. */ - public int[] executeBatch( String language, List query_list ) { - + public int[] executeBatch( String language, String namespace, List query_list ) { + checkNamespace( language, namespace ); polyconnection.openIfNeeded(); switch ( language.toLowerCase() ) { @@ -238,4 +244,17 @@ private RuntimeException translateSQLException( SQLException e ) { return new RuntimeException( "SQL execution failed: " + e.getMessage(), e ); } + + private static void checkNamespace( String language, String namespace ) { + if ( namespace == null || namespace.isEmpty() ) { + if ( language.equalsIgnoreCase( "sql" ) ) { + // fine: default namespace is used implicitly + } else { + throw new IllegalArgumentException( + "For " + language + " queries a namespace must be specified" + ); + } + } + } + } diff --git a/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java b/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java index adcab13..2a680df 100644 --- a/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java +++ b/app/src/test/java/polyphenyconnector/PolyphenyConnectionTestHelper.java @@ -1,7 +1,6 @@ package polyphenyconnector; import java.sql.DriverManager; -import java.sql.SQLException; public class PolyphenyConnectionTestHelper { @@ -32,10 +31,11 @@ static void waitForPolypheny() throws Exception { } } - + /* public static void ensurePostgresAdapter( PolyphenyConnection conn ) throws SQLException { QueryExecutor exec = new QueryExecutor( conn ); - exec.execute( "sql", "CREATE ADAPTER IF NOT EXISTS postgresql1 USING postgresql ..." ); + exec.execute( "sql", "unittest_namespace", "CREATE ADAPTER IF NOT EXISTS postgresql1 USING postgresql ..." ); } + */ } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java index 8df5168..905df47 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; + import java.util.Arrays; import java.util.List; @@ -24,22 +25,22 @@ static void setUpNamespaceAndTable() throws Exception { // Delete any TABLE called and any NAMESPACE if it exists. This is important so we can insert it // cleanly, in case tests break mid run the cleanup "@Afterall" might not have been executed properly. try { - myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); - myexecutor.execute( "sql", "DROP NAMESPACE IF EXISTS unittest_namespace" ); + myexecutor.execute( "sql", "unittest_namespace", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "unittest_namespace", "DROP NAMESPACE IF EXISTS unittest_namespace" ); } catch ( Exception ignored ) { } // Creates the NAMESPACE and TABLE . - myexecutor.execute( "sql", "CREATE NAMESPACE unittest_namespace" ); - myexecutor.execute( "sql", "CREATE TABLE unittest_namespace.unittest_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY(id))" ); + myexecutor.execute( "sql", "unittest_namespace", "CREATE NAMESPACE unittest_namespace" ); + myexecutor.execute( "sql", "unittest_namespace", "CREATE TABLE unittest_namespace.unittest_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY(id))" ); // 2. Setup tables for executeBatch() // Delete any tables that might still exist as described before. try { - myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.batch_table" ); + myexecutor.execute( "sql", "unittest_namespace", "DROP TABLE IF EXISTS unittest_namespace.batch_table" ); } catch ( Exception ignored ) { } - myexecutor.execute( "sql", "CREATE TABLE unittest_namespace.batch_table (" + "emp_id INT NOT NULL, " + "name VARCHAR(100), " + "gender VARCHAR(10), " + "birthday DATE, " + "employee_id INT, " + "PRIMARY KEY(emp_id))" ); + myexecutor.execute( "sql", "unittest_namespace", "CREATE TABLE unittest_namespace.batch_table (" + "emp_id INT NOT NULL, " + "name VARCHAR(100), " + "gender VARCHAR(10), " + "birthday DATE, " + "employee_id INT, " + "PRIMARY KEY(emp_id))" ); } @@ -47,24 +48,24 @@ static void setUpNamespaceAndTable() throws Exception { @AfterAll static void tearDownNamespaceAndTable() { // Cleans up the TABLE and NAMESPACE we created so we leave no trace after the tests. - myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); - myexecutor.execute( "sql", "DROP TABLE IF EXISTS unittest_namespace.batch_table" ); - myexecutor.execute( "sql", "DROP NAMESPACE IF EXISTS unittest_namespace" ); + myexecutor.execute( "sql", "unittest_namespace", "DROP TABLE IF EXISTS unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "unittest_namespace", "DROP TABLE IF EXISTS unittest_namespace.batch_table" ); + myexecutor.execute( "sql", "unittest_namespace", "DROP NAMESPACE IF EXISTS unittest_namespace" ); myconnection.close(); } @BeforeEach void clearTable() { - myexecutor.execute( "sql", "DELETE FROM unittest_namespace.unittest_table" ); - myexecutor.execute( "sql", "DELETE FROM unittest_namespace.batch_table" ); + myexecutor.execute( "sql", "unittest_namespace", "DELETE FROM unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "unittest_namespace", "DELETE FROM unittest_namespace.batch_table" ); } @AfterEach void clearTableAfter() { - myexecutor.execute( "sql", "DELETE FROM unittest_namespace.unittest_table" ); - myexecutor.execute( "sql", "DELETE FROM unittest_namespace.batch_table" ); + myexecutor.execute( "sql", "unittest_namespace", "DELETE FROM unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "unittest_namespace", "DELETE FROM unittest_namespace.batch_table" ); } // ───────────────────────────── @@ -74,7 +75,7 @@ void clearTableAfter() { @Test void testScalarLiteral() { - Object result = myexecutor.execute( "sql", "SELECT 42 AS answer" ); + Object result = myexecutor.execute( "sql", "", "SELECT 42 AS answer" ); assertTrue( result instanceof Integer, "Expected an integer scalar" ); assertEquals( 42, result ); } @@ -82,14 +83,14 @@ void testScalarLiteral() { @Test void testEmptyLiteral() { - Object result = myexecutor.execute( "sql", "SELECT * FROM (SELECT 1) t WHERE 1=0" ); + Object result = myexecutor.execute( "sql", "", "SELECT * FROM (SELECT 1) t WHERE 1=0" ); assertNull( result, "Query with no rows should return null" ); } @Test void testTableLiteral() { - Object result = myexecutor.execute( "sql", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); + Object result = myexecutor.execute( "sql", "", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); assertTrue( result instanceof Object[], "Expected tabular result" ); Object[] arr = (Object[]) result; @@ -110,7 +111,7 @@ void testTableLiteral() { @Test void testInsert() { // Insert id = 1 and name = Alice into the table. - Object result = myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); assertTrue( result instanceof Integer, "Expected an integer." ); assertEquals( result, 1, "result should equal 1." ); } @@ -119,10 +120,10 @@ void testInsert() { @Test void testInsertAndSelect() { // Insert id = 1 and name = Alice into the table. - myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); + myexecutor.execute( "sql", "unittest_namespace", "INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); // Query the result from the table. - Object result = myexecutor.execute( "sql", "SELECT id, name FROM unittest_namespace.unittest_table" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECT id, name FROM unittest_namespace.unittest_table" ); // Test that the result comes back as array. System.out.println( "Result is: " + result ); @@ -141,8 +142,8 @@ void testInsertAndSelect() { @Test void testScalarFromTable() { - myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Carol')" ); - Object result = myexecutor.execute( "sql", "SELECT id FROM unittest_namespace.unittest_table WHERE name = 'Carol'" ); + myexecutor.execute( "sql", "unittest_namespace", "INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Carol')" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECT id FROM unittest_namespace.unittest_table WHERE name = 'Carol'" ); assertTrue( result instanceof Integer, "Expected scalar integer result" ); assertEquals( 2, result ); } @@ -151,11 +152,11 @@ void testScalarFromTable() { @Test void testInsertAndSelectMultipleRows() { // Insert id = 1,2 and name = Alice, Bob into the table. - myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); - myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Bob')" ); + myexecutor.execute( "sql", "unittest_namespace", "INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); + myexecutor.execute( "sql", "unittest_namespace", "INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Bob')" ); // Query the result from the table. - Object result = myexecutor.execute( "sql", "SELECT id, name FROM unittest_namespace.unittest_table ORDER BY id" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECT id, name FROM unittest_namespace.unittest_table ORDER BY id" ); // Check the contents of the query are correct. Object[] arr = (Object[]) result; @@ -178,11 +179,11 @@ void testInsertAndSelectMultipleRows() { void testQueryWithSpaces() { // Insert Bob into table. // Insert id = 1,2 and name = Alice, Bob into the table. - myexecutor.execute( "sql", " INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); - myexecutor.execute( "sql", " INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Bob')" ); + myexecutor.execute( "sql", "unittest_namespace", " INSERT INTO unittest_namespace.unittest_table VALUES (1, 'Alice')" ); + myexecutor.execute( "sql", "unittest_namespace", " INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Bob')" ); // Query the result from the table. - Object result = myexecutor.execute( "sql", "SELECT id, name FROM unittest_namespace.unittest_table ORDER BY id" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECT id, name FROM unittest_namespace.unittest_table ORDER BY id" ); // Check the contents of the query are correct. Object[] arr = (Object[]) result; @@ -205,13 +206,13 @@ void testQueryWithSpaces() { void testDeleteFromTable() { // Insert Bob into table. - myexecutor.execute( "sql", "INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Bob')" ); + myexecutor.execute( "sql", "unittest_namespace", "INSERT INTO unittest_namespace.unittest_table VALUES (2, 'Bob')" ); // Delete Bob from table. - myexecutor.execute( "sql", "DELETE FROM unittest_namespace.unittest_table" ); + myexecutor.execute( "sql", "unittest_namespace", "DELETE FROM unittest_namespace.unittest_table" ); // Test that the query comes back null. - Object result = myexecutor.execute( "sql", "SELECT * FROM unittest_namespace.unittest_table WHERE name = 'Bob'" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECT * FROM unittest_namespace.unittest_table WHERE name = 'Bob'" ); assertNull( result, "After DELETE the table should be empty" ); } @@ -237,7 +238,7 @@ void testBatchInsertEmployees() { ); // Do the batch execution using executeBatch(...) - int[] counts = myexecutor.executeBatch( "sql", queries ); + int[] counts = myexecutor.executeBatch( "sql", "unittest_namespace", queries ); // Test that the lenghth of the counts vector is 13 (for 13 queries in the queries liest). assertEquals( 13, counts.length, "Batch should return 13 results" ); @@ -248,7 +249,7 @@ void testBatchInsertEmployees() { } // Test the result has the correct type - Object result = myexecutor.execute( "sql", "SELECT COUNT(*) FROM unittest_namespace.batch_table" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECT COUNT(*) FROM unittest_namespace.batch_table" ); assertTrue( result instanceof Long || result instanceof Integer ); // Test the rowcount is correct. @@ -268,11 +269,11 @@ void testBatchRollbackOnFailure() { // Run the ill posed batch query and test an exception is thrown. assertThrows( RuntimeException.class, () -> { - myexecutor.executeBatch( "sql", queries ); + myexecutor.executeBatch( "sql", "unittest_namespace", queries ); } ); // Query the whole table to make sure it is really empty. - Object result = myexecutor.execute( "sql", "SELECt * FROM unittest_namespace.batch_table" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECt * FROM unittest_namespace.batch_table" ); // Test the query comes back as null i.e. the executeBatch has indeed been rolled back and the table is unchanged assertNull( result ); @@ -284,7 +285,7 @@ void testConnectionFailure() { assertThrows( RuntimeException.class, () -> { PolyphenyConnection badConn = new PolyphenyConnection( "localhost", 9999, "pa", "" ); QueryExecutor badExec = new QueryExecutor( badConn ); - badExec.execute( "sql", "SELECT 1" ); // should fail to connect + badExec.execute( "sql", "unittest_namespace", "SELECT 1" ); // should fail to connect } ); } @@ -292,7 +293,7 @@ void testConnectionFailure() { @Test void testSyntaxError() { RuntimeException ex = assertThrows( RuntimeException.class, () -> { - myexecutor.execute( "sql", "SELEC WRONG FROM nowhere" ); // typo: SELEC + myexecutor.execute( "sql", "unittest_namespace", "SELEC WRONG FROM nowhere" ); // typo: SELEC } ); assertTrue( ex.getMessage().contains( "Syntax error" ) || ex.getMessage().contains( "execution failed" ), @@ -308,10 +309,10 @@ void testCommitFailureRollback() { ); assertThrows( RuntimeException.class, () -> { - myexecutor.executeBatch( "sql", queries ); + myexecutor.executeBatch( "sql", "unittest_namespace", queries ); } ); - Object result = myexecutor.execute( "sql", "SELECT * FROM unittest_namespace.batch_table" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECT * FROM unittest_namespace.batch_table" ); assertNull( result, "Batch should have rolled back and left the table empty" ); } From 316fd3d7209ed4f69037753f1d5e2617936e4d74 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 27 Sep 2025 22:04:40 +0200 Subject: [PATCH 32/44] I reverted back to original plan to use executeQuery/executeUpdate in the Relational part because the execute() function from the multimodel extension does not expose the needed metadata (for example to get the column names and column rows). In this version SQL works as intended and all SQL tests pass. I also implemented some unfinished changes for the Document part but this was commented out because it still has bugs. --- .vscode/settings.json | 4 + app/bin/main/PolyphenyWrapperTest.m | 8 +- app/build.gradle | 4 + app/src/main/java/PolyphenyWrapperTest.m | 8 +- .../polyphenyconnector/QueryExecutor.java | 156 ++++++++++++++---- .../QueryExecutorTestMQL.java | 123 ++++++++++++++ ...torTest.java => QueryExecutorTestSQL.java} | 4 +- 7 files changed, 263 insertions(+), 44 deletions(-) create mode 100644 app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java rename app/src/test/java/polyphenyconnector/{QueryExecutorTest.java => QueryExecutorTestSQL.java} (98%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2b6c1a4..9462d61 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,15 +5,19 @@ "java.project.referencedLibraries": [], "cSpell.words": [ "Afterall", + "disp", "empid", "emps", "JDBC", "johnrengelman", "mongoql", "myexecutor", + "Polpyheny's", "polyconnection", "Polypheny", "polyphenyconnector", + "stringbuilder", + "varargin", "wrongpass", "wronguser" ], diff --git a/app/bin/main/PolyphenyWrapperTest.m b/app/bin/main/PolyphenyWrapperTest.m index 7aa372d..3952634 100644 --- a/app/bin/main/PolyphenyWrapperTest.m +++ b/app/bin/main/PolyphenyWrapperTest.m @@ -47,21 +47,21 @@ function testEmpty(testCase) function testBatchInsert(testCase) % Prepare table - testCase.conn.query("sql" ,"DROP TABLE IF EXISTS batch_test"); - testCase.conn.query("sql" ,"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("sql" , "" , "DROP TABLE IF EXISTS batch_test"); + testCase.conn.query("sql" , "" , "CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); % Batch insert 2 rows queries = { ... "INSERT INTO batch_test VALUES (1,'Alice')", ... "INSERT INTO batch_test VALUES (2,'Bob')" ... }; - result = testCase.conn.queryBatch("sql" ,queries); + result = testCase.conn.queryBatch("sql" , "" , queries); % Verify JDBC return codes testCase.verifyEqual(result, [1 1]); % Verify table contents - T = testCase.conn.query("sql" ,"SELECT id, name FROM batch_test ORDER BY id"); + T = testCase.conn.query("sql" , "" ,"SELECT id, name FROM batch_test ORDER BY id"); testCase.verifyEqual(T.id, [1; 2]); testCase.verifyEqual(string(T.name), ["Alice"; "Bob"]); end diff --git a/app/build.gradle b/app/build.gradle index f3f29ac..068b9e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,6 +14,10 @@ dependencies { // JDBC driver: keep separate in toolbox/libs/, not bundled into the shaded JAR compileOnly files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) runtimeOnly files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) + + // Multimodel extension (make sure file exists at repo-root libs/) + runtimeOnly files( rootProject.file('libs/polypheny-jdbc-multimodel-2.3.jar') ) + testRuntimeOnly files( rootProject.file('libs/polypheny-jdbc-multimodel-2.3.jar') ) // JUnit 5 testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' diff --git a/app/src/main/java/PolyphenyWrapperTest.m b/app/src/main/java/PolyphenyWrapperTest.m index 7aa372d..3952634 100644 --- a/app/src/main/java/PolyphenyWrapperTest.m +++ b/app/src/main/java/PolyphenyWrapperTest.m @@ -47,21 +47,21 @@ function testEmpty(testCase) function testBatchInsert(testCase) % Prepare table - testCase.conn.query("sql" ,"DROP TABLE IF EXISTS batch_test"); - testCase.conn.query("sql" ,"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("sql" , "" , "DROP TABLE IF EXISTS batch_test"); + testCase.conn.query("sql" , "" , "CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); % Batch insert 2 rows queries = { ... "INSERT INTO batch_test VALUES (1,'Alice')", ... "INSERT INTO batch_test VALUES (2,'Bob')" ... }; - result = testCase.conn.queryBatch("sql" ,queries); + result = testCase.conn.queryBatch("sql" , "" , queries); % Verify JDBC return codes testCase.verifyEqual(result, [1 1]); % Verify table contents - T = testCase.conn.query("sql" ,"SELECT id, name FROM batch_test ORDER BY id"); + T = testCase.conn.query("sql" , "" ,"SELECT id, name FROM batch_test ORDER BY id"); testCase.verifyEqual(T.id, [1; 2]); testCase.verifyEqual(string(T.name), ["Alice"; "Bob"]); end diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 04d7022..5627a54 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -1,8 +1,11 @@ package polyphenyconnector; import java.sql.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; + +import org.polypheny.jdbc.PolyConnection; +import org.polypheny.jdbc.multimodel.*; +import org.polypheny.jdbc.types.*; public class QueryExecutor { @@ -22,6 +25,19 @@ public QueryExecutor( PolyphenyConnection polyconnection ) { } + /** + * @Description + * - Executes the query depending on the language given by the user + * + * @param language: The database language that is used (e.g. SQL, MongoQL,Cypher) + * @param namespace: The namespace in the query string. For SQL this argument as no effect as we use JDBC's + * executeQuery(...)/executeUpdate(...). For MQL this argument will be passed to JDBC's execute(...) function. For further + * information consult Polpyheny's web documentation. + * @param query: The query-text to be executed (e.g. FROM emps SELECT *) + * + * @return: ResultToMatlab(rs) which is a Matlab compatible object that is cast to the Matlab user. + **/ + /** * @Description * - Executes the query depending on the language given by the user @@ -35,8 +51,14 @@ public QueryExecutor( PolyphenyConnection polyconnection ) { * @return: ResultToMatlab(rs) which is a Matlab compatible object that is cast to the Matlab user. **/ public Object execute( String language, String namespace, String query ) { - checkNamespace( language, namespace ); polyconnection.openIfNeeded(); + try { + Connection conn = polyconnection.getConnection(); + conn.setAutoCommit( true ); + } catch ( SQLException e ) { + throw translateSQLException( e ); + } + switch ( language.toLowerCase() ) { default: throw new UnsupportedOperationException( "Unsupported language: " + language ); @@ -65,39 +87,37 @@ public Object execute( String language, String namespace, String query ) { throw new RuntimeException( "SQL execution failed: " + e.getMessage(), e ); } - case "mongoql": - try ( Statement stmt = polyconnection.getConnection().createStatement(); ResultSet rs = stmt.executeQuery( query ) ) { - - List Result = new ArrayList<>(); - ResultSetMetaData meta = rs.getMetaData(); - int colCount = meta.getColumnCount(); - - while ( rs.next() ) { - if ( colCount == 1 ) { - Result.add( rs.getObject( 1 ) ); - } else { - Object[] row = new Object[colCount]; - for ( int i = 0; i < colCount; i++ ) { - row[i] = rs.getObject( i + 1 ); - } - Result.add( row ); - } - } + case "mongo": + try { - if ( Result.isEmpty() ) { - return null; // empty query we return null - } else if ( Result.size() == 1 && colCount == 1 ) { - return Result.get( 0 ); // single scalar or single String - } else if ( colCount == 1 ) { - return Result.toArray( new String[0] ); // multiple JSON docs (i.e. a collection) - } else { - return Result.toArray( new Object[0] ); // fallback - } + // Get the connection variable from the PolyphenyConnection.java class using the getter + Connection connection = polyconnection.getConnection(); + + // Unwrap Connection connection to the JDBC Driver-supplied PolyConnection polyConnection + PolyConnection polyConnection = connection.unwrap( PolyConnection.class ); + + // Create a PolyStatement object to call .execute(...) method of the JDBC- Driver on + PolyStatement polyStatement = polyConnection.createPolyStatement(); + // Call the execute(...) function on the polyStatement + Result result = polyStatement.execute( namespace, language, query ); + + switch ( result.getResultType() ) { + case DOCUMENT: + DocumentResult documentResult = result.unwrap( DocumentResult.class ); + //return DocumentToMatlab( documentResult ); + case SCALAR: + ScalarResult scalarResult = result.unwrap( ScalarResult.class ); + return scalarResult.getScalar(); + + default: + throw new UnsupportedOperationException( "Unhandled result type: " + result.getResultType() ); + } + } catch ( SQLException e ) { + throw translateSQLException( e ); } catch ( Exception e ) { - throw new RuntimeException( "MongoQL execution failed: " + e.getMessage(), e ); + throw new RuntimeException( "Mongo execution failed: " + e.getMessage(), e ); } - case "cypher": throw new UnsupportedOperationException( "Cypher execution not yet implemented." ); } @@ -119,7 +139,6 @@ public Object execute( String language, String namespace, String query ) { * n: n rows were updated, 0: no rows were touched. */ public int[] executeBatch( String language, String namespace, List query_list ) { - checkNamespace( language, namespace ); polyconnection.openIfNeeded(); switch ( language.toLowerCase() ) { @@ -221,6 +240,18 @@ public Object SQLResultToMatlab( ResultSet rs ) throws Exception { return new Object[]{ colNames, ResultArray }; } + /* + private String[] DocumentToMatlab( DocumentResult documentResult ) { + List docs = new ArrayList<>(); + Iterator documentIterator = documentResult.iterator(); // Call iterator for PolyDocumentResult + while ( documentIterator.hasNext() ) { + PolyDocument document = documentIterator.next(); + docs.add( NestedPolyDocumentToString( document ) ); + } + return docs.toArray( new String[0] ); + } + */ + /** * @Description @@ -228,7 +259,7 @@ public Object SQLResultToMatlab( ResultSet rs ) throws Exception { * execute or executeBatch are translated in a user-interpretable error to be propagated to Matlab, instead of just failing * with an obscure error/exception. * - * @param e The exception caught. 08 denotes an error with the Polypheny connetion, 42 denotes a + * @param e The exception caught. 08 denotes an error with the Polypheny connection, 42 denotes a * @return */ private RuntimeException translateSQLException( SQLException e ) { @@ -244,6 +275,38 @@ private RuntimeException translateSQLException( SQLException e ) { return new RuntimeException( "SQL execution failed: " + e.getMessage(), e ); } + /* + private String NestedPolyDocumentToString( PolyDocument doc ) { + StringBuilder stringbuilder = new StringBuilder(); + stringbuilder.append( "{" ); + + Iterator> it = doc.entrySet().iterator(); + while ( it.hasNext() ) { + Map.Entry entry = it.next(); + String key = entry.getKey().toString(); + TypedValue value = entry.getValue(); + + stringbuilder.append( "\"" ).append( key ).append( "\":" ); + + if ( value instanceof PolyDocument ) { + // recurse for nested docs + stringbuilder.append( NestedPolyDocumentToString( (PolyDocument) value ) ); + } else if ( value != null ) { + stringbuilder.append( "\"" ).append( value.toString() ).append( "\"" ); + } else { + stringbuilder.append( "null" ); + } + + if ( it.hasNext() ) { + stringbuilder.append( "," ); + } + } + + stringbuilder.append( "}" ); + return stringbuilder.toString(); + } + */ + private static void checkNamespace( String language, String namespace ) { if ( namespace == null || namespace.isEmpty() ) { @@ -257,4 +320,29 @@ private static void checkNamespace( String language, String namespace ) { } } + + /** + * @Description + * This function determines the operation (e.g. "find" or "insertOne") of a MongoQL query. This is important to distinguish + * whether to use executeUpdate or executeQuery. Functionality was moved to a function (instead of handling it like for SQL) + * because in MongoQL queries the operation isn't as easy to determine. + * MQL queries are always of the form ..() + * + * @param q The query text of type String + * @return + */ + private static String extractMongoOperation( String q ) { + String query = q.trim(); + int paren = query.indexOf( '(' ); // get the position of the first "(" in the query. + if ( paren < 0 ) { + return ""; // return an empty String if no ( was found + } + int lastDot = query.lastIndexOf( '.', paren ); // get the position of the last "." before the "(". + if ( lastDot < 0 ) { + return ""; // return an empty string if no dot was found + } + String operation = query.substring( lastDot + 1, paren ).trim(); + return operation; // return the + } + } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java new file mode 100644 index 0000000..f57b17e --- /dev/null +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java @@ -0,0 +1,123 @@ +package polyphenyconnector; + +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; + +public class QueryExecutorTestMQL { + + private static PolyphenyConnection myconnection; + private static QueryExecutor myexecutor; + + + @BeforeAll + static void setUpNamespaceAndCollection() throws Exception { + PolyphenyConnectionTestHelper.waitForPolypheny(); + Thread.sleep( 4000 ); + myconnection = new PolyphenyConnection( "localhost", 20590, "pa", "" ); + myexecutor = new QueryExecutor( myconnection ); + + // Trigger implicit creation by inserting a dummy doc + try { + } catch ( Exception ignoredException ) { + + } + } + + /* + @AfterAll + static void tearDownNamespaceAndCollection() { + try { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.drop()" ); + } catch ( Exception ignored ) { + } + myconnection.close(); + } + */ + + + @BeforeEach + void clearCollection() { + try { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.drop()" ); + myexecutor.execute( "mongo", "mongotest", "db.createCollection(\"unittest_collection\")" ); + } catch ( Exception ignored ) { + } + } + + /* + @AfterEach + void clearCollectionAfter() { + try { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.drop()" ); + } catch ( Exception ignored ) { + } + } + */ + + + @Test + void testInsertAndFindReturnsDocumentArray() { + // insert a document (namespace passed separately; query contains only collection.operation) + + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":14})" ); + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":20})" ); + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":24})" ); + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":30, \"adress\": {\"Country\": \"Switzerland\", \"Code\": 4051}})" ); + Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({\"age\": {$gt:29}})" ); + + assertTrue( result instanceof String[], "expected a String[] for DocumentResult" ); + String[] docs = (String[]) result; + assertEquals( 1, docs.length ); + //System.out.println( docs[0] ); + assertTrue( docs[0].contains( "\"age\":14" ) ); + } + + + @Disabled + @Test + void testCountDocumentsReturnsScalar() { + // ensure empty, insert one then count + myexecutor.execute( "mongo", "unittest_collection", + "unittest_namespace.unittest_coll.insertOne({\"_id\":2,\"name\":\"Bob\"})" ); + + Object out = myexecutor.execute( "mongo", "unittest_namespace", + "unittest_namespace.unittest_coll.countDocuments({})" ); + + assertTrue( out instanceof Number, "Expected numeric scalar from countDocuments" ); + assertEquals( 1, ((Number) out).intValue() ); + } + + + @Disabled + @Test + void testFindOnEmptyCollectionReturnsEmptyArray() { + // ensure empty + myexecutor.execute( "mongo", "unittest_namespace", + "unittest_namespace.unittest_coll.deleteMany({})" ); + + Object out = myexecutor.execute( "mongo", "unittest_namespace", + "unittest_namespace.unittest_coll.find({})" ); + + assertTrue( out instanceof String[], "expected String[] even for empty cursor" ); + String[] docs = (String[]) out; + assertEquals( 0, docs.length ); + } + + + @Disabled + @Test + void testInsertManyAndFindMultiple() { + myexecutor.execute( "mongo", "unittest_namespace", + "unittest_namespace.unittest_coll.insertOne({\"_id\":10,\"name\":\"A\"})" ); + myexecutor.execute( "mongo", "unittest_namespace", + "unittest_namespace.unittest_coll.insertOne({\"_id\":11,\"name\":\"B\"})" ); + + Object out = myexecutor.execute( "mongo", "unittest_namespace", + "unittest_namespace.unittest_coll.find({})" ); + + assertTrue( out instanceof String[] ); + String[] docs = (String[]) out; + assertEquals( 2, docs.length ); + } + +} diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java similarity index 98% rename from app/src/test/java/polyphenyconnector/QueryExecutorTest.java rename to app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java index 905df47..c2b4d07 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTest.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java @@ -6,7 +6,7 @@ import java.util.Arrays; import java.util.List; -public class QueryExecutorTest { +public class QueryExecutorTestSQL { private static PolyphenyConnection myconnection; private static QueryExecutor myexecutor; @@ -90,7 +90,7 @@ void testEmptyLiteral() { @Test void testTableLiteral() { - Object result = myexecutor.execute( "sql", "", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); + Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); assertTrue( result instanceof Object[], "Expected tabular result" ); Object[] arr = (Object[]) result; From abe7f23802b0088bb555470375f05066e72196c1 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Tue, 30 Sep 2025 22:31:18 +0200 Subject: [PATCH 33/44] Implemented all necessary changes for the Document part --- app/build.gradle | 27 +-- .../polyphenyconnector/QueryExecutor.java | 183 ++++++++++++++---- .../QueryExecutorTestMQL.java | 114 +++++++++-- .../QueryExecutorTestSQL.java | 46 +++++ 4 files changed, 307 insertions(+), 63 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 068b9e6..8fbf696 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,19 +8,20 @@ application { mainClass = 'QuickTest' } // currently running QuickTest.java as a repositories { mavenCentral() } dependencies { - // SLF4J implementation to silence warnings (bundled into the shaded JAR) - implementation 'org.slf4j:slf4j-simple:2.0.16' - - // JDBC driver: keep separate in toolbox/libs/, not bundled into the shaded JAR - compileOnly files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) - runtimeOnly files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) - - // Multimodel extension (make sure file exists at repo-root libs/) - runtimeOnly files( rootProject.file('libs/polypheny-jdbc-multimodel-2.3.jar') ) - testRuntimeOnly files( rootProject.file('libs/polypheny-jdbc-multimodel-2.3.jar') ) - - // JUnit 5 - testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + // SLF4J implementation to silence warnings + implementation 'org.slf4j:slf4j-simple:2.0.16' + + // Polypheny JDBC driver + implementation files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) + testImplementation files(rootProject.file('libs/polypheny-jdbc-driver-2.3.jar')) + + // Multimodel extension JAR (if you really need it) + runtimeOnly files(rootProject.file('libs/polypheny-jdbc-multimodel-2.3.jar')) + testRuntimeOnly files(rootProject.file('libs/polypheny-jdbc-multimodel-2.3.jar')) + + // JUnit 5 API + engine + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2' } shadowJar { diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 5627a54..6497699 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -1,5 +1,6 @@ package polyphenyconnector; +import java.math.BigDecimal; import java.sql.*; import java.util.*; @@ -105,7 +106,7 @@ public Object execute( String language, String namespace, String query ) { switch ( result.getResultType() ) { case DOCUMENT: DocumentResult documentResult = result.unwrap( DocumentResult.class ); - //return DocumentToMatlab( documentResult ); + return DocumentToMatlab( documentResult ); case SCALAR: ScalarResult scalarResult = result.unwrap( ScalarResult.class ); return scalarResult.getScalar(); @@ -171,7 +172,7 @@ public int[] executeBatch( String language, String namespace, List query throw new RuntimeException( "Failed to manage transaction", e ); } - case "mongoql": + case "mongo": throw new UnsupportedOperationException( "MongoQL batch execution not yet implemented." ); case "cypher": @@ -240,7 +241,7 @@ public Object SQLResultToMatlab( ResultSet rs ) throws Exception { return new Object[]{ colNames, ResultArray }; } - /* + private String[] DocumentToMatlab( DocumentResult documentResult ) { List docs = new ArrayList<>(); Iterator documentIterator = documentResult.iterator(); // Call iterator for PolyDocumentResult @@ -250,7 +251,149 @@ private String[] DocumentToMatlab( DocumentResult documentResult ) { } return docs.toArray( new String[0] ); } - */ + + + private String NestedPolyDocumentToString( PolyDocument document ) { + StringBuilder stringbuilder = new StringBuilder(); + stringbuilder.append( "{" ); + + Iterator> iterator = document.entrySet().iterator(); + while ( iterator.hasNext() ) { + Map.Entry entry = iterator.next(); + String key = entry.getKey().toString(); + TypedValue value = entry.getValue(); + + stringbuilder.append( "\"" ).append( key ).append( "\":" ); + + try { + switch ( value.getValueCase() ) { + case DOCUMENT: + stringbuilder.append( NestedPolyDocumentToString( value.asDocument() ) ); + break; + case LIST: + stringbuilder.append( NestedListToString( value.asArray() ) ); + break; + case BOOLEAN: + stringbuilder.append( value.asBoolean() ); + break; + case INTEGER: + stringbuilder.append( value.asInt() ); + break; + case LONG: + stringbuilder.append( value.asLong() ); + break; + case DOUBLE: + stringbuilder.append( value.asDouble() ); + break; + case FLOAT: + stringbuilder.append( value.asFloat() ); + break; + case BIG_DECIMAL: + stringbuilder.append( value.asBigDecimal().toPlainString() ); // toPlainString() turns 1E-7 into 0.0000001 + break; + case STRING: + stringbuilder.append( "\"" ).append( escapeJson( value.asString() ) ).append( "\"" ); + break; + case DATE: + stringbuilder.append( "\"" ).append( value.asDate().toString() ).append( "\"" ); + break; + case TIME: + stringbuilder.append( "\"" ).append( value.asTime().toString() ).append( "\"" ); + break; + case TIMESTAMP: + stringbuilder.append( "\"" ).append( value.asTimestamp().toString() ).append( "\"" ); + break; + case INTERVAL: + stringbuilder.append( "\"" ).append( value.asInterval().toString() ).append( "\"" ); + break; + case BINARY: + stringbuilder.append( "\"" ).append( Base64.getEncoder().encodeToString( value.asBytes() ) ).append( "\"" ); + break; + case FILE: + stringbuilder.append( "\"" ).append( escapeJson( String.valueOf( value.asBlob() ) ) ).append( "\"" ); + break; + case NULL: + stringbuilder.append( "null" ); + break; + default: + stringbuilder.append( "\"" ).append( escapeJson( value.toString() ) ).append( "\"" ); + } + } catch ( SQLException e ) { + throw new RuntimeException( "TypedValue error: " + e.getMessage(), e ); + } + + if ( iterator.hasNext() ) { + stringbuilder.append( "," ); + } + } + + stringbuilder.append( "}" ); + return stringbuilder.toString(); + } + + + private String NestedListToString( Array array ) { + StringBuilder sb = new StringBuilder(); + sb.append( "[" ); + + try { + Object[] elems = (Object[]) array.getArray(); + for ( int i = 0; i < elems.length; i++ ) { + Object e = elems[i]; + + if ( e instanceof PolyDocument ) { + sb.append( NestedPolyDocumentToString( (PolyDocument) e ) ); + } else if ( e instanceof Array ) { + sb.append( NestedListToString( (Array) e ) ); + } else if ( e instanceof Boolean ) { + sb.append( (Boolean) e ); + } else if ( e instanceof Integer ) { + sb.append( (Integer) e ); + } else if ( e instanceof Long ) { + sb.append( (Long) e ); + } else if ( e instanceof Double ) { + sb.append( (Double) e ); + } else if ( e instanceof Float ) { + sb.append( (Float) e ); + } else if ( e instanceof BigDecimal ) { + sb.append( ((BigDecimal) e).toPlainString() ); + } else if ( e instanceof String ) { + sb.append( "\"" ).append( escapeJson( (String) e ) ).append( "\"" ); + } else if ( e instanceof java.sql.Date + || e instanceof java.sql.Time + || e instanceof java.sql.Timestamp ) { + sb.append( "\"" ).append( e.toString() ).append( "\"" ); + } else if ( e instanceof byte[] ) { + sb.append( "\"" ).append( Base64.getEncoder().encodeToString( (byte[]) e ) ).append( "\"" ); + } else if ( e == null ) { + sb.append( "null" ); + } else { + sb.append( "\"" ).append( escapeJson( e.toString() ) ).append( "\"" ); + } + + if ( i < elems.length - 1 ) { + sb.append( "," ); + } + } + } catch ( SQLException ex ) { + throw new RuntimeException( "List serialization error: " + ex.getMessage(), ex ); + } + + sb.append( "]" ); + return sb.toString(); + } + + + /** + * @Description + * This function takes makes sure that the escapes of queries are handled correctly in Strings when appending. + * e.g. " C\mypath " must be converted into " C\\mypath " because "\" is an operator sign like in Latex + * @param string + * @return string: The string with the proper escape sequences + */ + private static String escapeJson( String string ) { + return string.replace( "\\", "\\\\" ).replace( "\"", "\\\"" ); + } /** @@ -275,38 +418,6 @@ private RuntimeException translateSQLException( SQLException e ) { return new RuntimeException( "SQL execution failed: " + e.getMessage(), e ); } - /* - private String NestedPolyDocumentToString( PolyDocument doc ) { - StringBuilder stringbuilder = new StringBuilder(); - stringbuilder.append( "{" ); - - Iterator> it = doc.entrySet().iterator(); - while ( it.hasNext() ) { - Map.Entry entry = it.next(); - String key = entry.getKey().toString(); - TypedValue value = entry.getValue(); - - stringbuilder.append( "\"" ).append( key ).append( "\":" ); - - if ( value instanceof PolyDocument ) { - // recurse for nested docs - stringbuilder.append( NestedPolyDocumentToString( (PolyDocument) value ) ); - } else if ( value != null ) { - stringbuilder.append( "\"" ).append( value.toString() ).append( "\"" ); - } else { - stringbuilder.append( "null" ); - } - - if ( it.hasNext() ) { - stringbuilder.append( "," ); - } - } - - stringbuilder.append( "}" ); - return stringbuilder.toString(); - } - */ - private static void checkNamespace( String language, String namespace ) { if ( namespace == null || namespace.isEmpty() ) { diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java index f57b17e..b3995b4 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java @@ -18,12 +18,13 @@ static void setUpNamespaceAndCollection() throws Exception { // Trigger implicit creation by inserting a dummy doc try { + myexecutor.execute( "mongo", "mongotest", "db.createCollection(\"unittest_collection\")" ); } catch ( Exception ignoredException ) { } } - /* + @AfterAll static void tearDownNamespaceAndCollection() { try { @@ -32,31 +33,50 @@ static void tearDownNamespaceAndCollection() { } myconnection.close(); } - */ @BeforeEach void clearCollection() { try { - myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.drop()" ); - myexecutor.execute( "mongo", "mongotest", "db.createCollection(\"unittest_collection\")" ); + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.deleteMany({})" ); } catch ( Exception ignored ) { } } - /* + @AfterEach void clearCollectionAfter() { try { - myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.drop()" ); + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.deleteMany({})" ); } catch ( Exception ignored ) { } } - */ @Test - void testInsertAndFindReturnsDocumentArray() { + void testInsertandDrop() { + // Query to make sure the unittest_collection is actually empty + Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + String[] docs = (String[]) result; + assertEquals( 0, docs.length ); + + // Insert entry into database + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":14})" ); + result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({\"age\":14})" ); + docs = (String[]) result; + assertEquals( 1, docs.length ); + assertTrue( docs[0].contains( "\"age\":14" ) ); + + // Drop entry + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.deleteMany({})" ); + result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({\"age\": {\"$eq\": 14}})" ); + docs = (String[]) result; + assertEquals( 0, docs.length ); + } + + + @Test + void testInsertManyAndNestedDocument() { // insert a document (namespace passed separately; query contains only collection.operation) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":14})" ); @@ -66,10 +86,56 @@ void testInsertAndFindReturnsDocumentArray() { Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({\"age\": {$gt:29}})" ); assertTrue( result instanceof String[], "expected a String[] for DocumentResult" ); - String[] docs = (String[]) result; + String[] docs = (String[]) result; // you can also do this quicker by String[] docs = (String[]) myexecuter.execute(...) assertEquals( 1, docs.length ); //System.out.println( docs[0] ); - assertTrue( docs[0].contains( "\"age\":14" ) ); + assertTrue( docs[0].contains( "\"age\":30" ) ); + } + + + @Test + void testBooleanField() { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"flag\":true})" ); + String[] docs = (String[]) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + assertTrue( docs[0].contains( "\"flag\":true" ) ); + } + + + @Test + void testIntegerAgeField() { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":42})" ); + String[] docs = (String[]) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + assertEquals( 1, docs.length ); + assertTrue( docs[0].contains( "\"age\":42" ) ); + } + + + @Test + void testStringField() { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"name\":\"Alice\"})" ); + String[] docs = (String[]) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + assertEquals( 1, docs.length ); + assertTrue( docs[0].contains( "\"name\":\"Alice\"" ) ); + } + + + @Test + void testLongField() { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"big\":1111111111111111111})" ); + Object r = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + String[] docs = (String[]) r; + assertEquals( 1, docs.length ); + assertTrue( docs[0].contains( "\"big\":1111111111111111111" ) ); + } + + + @Test + void testDoubleField() { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"pi\":3.14159})" ); + Object r = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + String[] docs = (String[]) r; + assertEquals( 1, docs.length ); + assertTrue( docs[0].contains( "\"pi\":3.14159" ) ); } @@ -78,13 +144,33 @@ void testInsertAndFindReturnsDocumentArray() { void testCountDocumentsReturnsScalar() { // ensure empty, insert one then count myexecutor.execute( "mongo", "unittest_collection", - "unittest_namespace.unittest_coll.insertOne({\"_id\":2,\"name\":\"Bob\"})" ); + "unittest_namespace.unittest_coll.insertOne({\"name\":\"Bob\"})" ); - Object out = myexecutor.execute( "mongo", "unittest_namespace", + Object result = myexecutor.execute( "mongo", "unittest_namespace", "unittest_namespace.unittest_coll.countDocuments({})" ); - assertTrue( out instanceof Number, "Expected numeric scalar from countDocuments" ); - assertEquals( 1, ((Number) out).intValue() ); + assertTrue( result instanceof Number, "Expected numeric scalar from countDocuments" ); + assertEquals( 1, ((Number) result).intValue() ); + } + + + @Test + void testListElementClasses() { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"mixed\":[{\"bar\":2},1,\"foo\"]})" ); + Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + String[] docs = (String[]) result; + System.out.println( docs[0] ); + } + + + @Disabled + @Test + void testArrayField() { + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"scores\":[1,2,3]})" ); + Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + String[] docs = (String[]) result; + assertEquals( 1, docs.length ); + assertTrue( docs[0].contains( "\"scores\":[1,2,3]" ) ); } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java index c2b4d07..93010ee 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java @@ -1,10 +1,16 @@ package polyphenyconnector; import org.junit.jupiter.api.*; +import org.polypheny.jdbc.PolyConnection; +import org.polypheny.jdbc.multimodel.*; +import org.polypheny.jdbc.types.TypedValue; + import static org.junit.jupiter.api.Assertions.*; +import java.sql.Connection; import java.util.Arrays; import java.util.List; +import java.util.Iterator; public class QueryExecutorTestSQL { @@ -316,4 +322,44 @@ void testCommitFailureRollback() { assertNull( result, "Batch should have rolled back and left the table empty" ); } + + @Test + // This test asserts that the column names aren't stored in the first row of the table for relational results. + // Thought that this might maybe be how it's implemented for relational results in execute(...) + void testRelationalResultFirstRowDirectly() throws Exception { + // Insert a row we can recognize + myexecutor.execute( + "sql", + "unittest_namespace", + "INSERT INTO unittest_namespace.unittest_table (id, name) VALUES (1, 'Alice')" + ); + + // Unwrap to PolyConnection and PolyStatement + Connection jdbcConn = myconnection.getConnection(); + PolyConnection polyConn = jdbcConn.unwrap( PolyConnection.class ); + PolyStatement polyStmt = polyConn.createPolyStatement(); + + // Run query directly through multimodel API + Result result = polyStmt.execute( + "unittest_namespace", + "sql", + "SELECT * FROM unittest_namespace.unittest_table" + ); + + assertEquals( Result.ResultType.RELATIONAL, result.getResultType() ); + + RelationalResult rr = result.unwrap( RelationalResult.class ); + Iterator it = rr.iterator(); + assertTrue( it.hasNext(), "Expected at least one row" ); + + PolyRow firstRow = it.next(); + assertEquals( 2, firstRow.getColumnCount(), "Expected 2 columns (id, name)" ); + + TypedValue idVal = firstRow.getValue( 0 ); + TypedValue nameVal = firstRow.getValue( 1 ); + + assertEquals( 1, idVal.asInt() ); + assertEquals( "Alice", nameVal.asString() ); + } + } From 6f54b2e69166f9966f3e16ff4ae45f1fefa4a0a2 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Wed, 1 Oct 2025 02:45:13 +0200 Subject: [PATCH 34/44] Implemented the executeBatch for Document. Refactored to executedBatchSql and executeBatchMongo because these two both need to return different types. SQL must return List as it only allows for executeUpdate like operations that return a 'number of columns modified' counter for every query, while Mongo allows both 'executeQuery' and 'executeUpdate' like operation (i.e. both read and write) and so the results are heterogeneous. Therefore the returntype here is List> which will then be passed to Matlab to decode using jsondecode --- .../polyphenyconnector/QueryExecutor.java | 289 +++++++++--------- .../QueryExecutorTestMQL.java | 166 +++++----- .../QueryExecutorTestSQL.java | 10 +- 3 files changed, 242 insertions(+), 223 deletions(-) diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 6497699..292b35b 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -1,6 +1,5 @@ package polyphenyconnector; -import java.math.BigDecimal; import java.sql.*; import java.util.*; @@ -104,12 +103,16 @@ public Object execute( String language, String namespace, String query ) { Result result = polyStatement.execute( namespace, language, query ); switch ( result.getResultType() ) { + case DOCUMENT: + // Always returns a List DocumentResult documentResult = result.unwrap( DocumentResult.class ); return DocumentToMatlab( documentResult ); + case SCALAR: ScalarResult scalarResult = result.unwrap( ScalarResult.class ); - return scalarResult.getScalar(); + long scalar = scalarResult.getScalar(); + return scalar; default: throw new UnsupportedOperationException( "Unhandled result type: " + result.getResultType() ); @@ -126,59 +129,77 @@ public Object execute( String language, String namespace, String query ) { } + // SQL convenience overload + public Object execute( String query ) { + return execute( "sql", "public", query ); + } + + /** * @Description - * This function is capable of executing a List of non-SELECT SQL statements in one single Matlab-Java crossing. All SQL statements - * except SELECT are supported. For further information consult the Polyphenys JDBC Driver documentation. + * This function is capable of executing a List of non-SELECT SQL statements in one single Matlab-Java crossing. + * All SQL statements except SELECT are supported. For further information consult the Polypheny JDBC Driver documentation. * - * @param language The database language that is used (e.g. SQL, MongoQL,Cypher) - * @param namespace: The namespace in the query string. For SQL this argument as no effect as we use JDBC's - * executeQuery(...)/executeUpdate(...). For MQL this argument will be passed to JDBC's execute(...) function. For further - * information consult Polpyheny's web documentation. - * @param query_list The list of query strings to be executed. - * @return int[] result An array of integers, where the i-th entry will denote for the i-th query how many rows were touched, e.g. + * @param queries The list of SQL query strings to be executed. + * @return List result A list of integers, where the i-th entry will denote for the i-th query how many rows were touched, e.g. * n: n rows were updated, 0: no rows were touched. */ - public int[] executeBatch( String language, String namespace, List query_list ) { + public List executeBatchSql( List queries ) { polyconnection.openIfNeeded(); - - switch ( language.toLowerCase() ) { - default: - throw new UnsupportedOperationException( "Unsupported language: " + language ); - - case "sql": - try { - polyconnection.beginTransaction(); - try ( Statement stmt = polyconnection.getConnection().createStatement() ) { - for ( String query : query_list ) { - String first = query.trim().toUpperCase(); - if ( first.startsWith( "SELECT" ) ) { - throw new UnsupportedOperationException( "Batch execution does not support SELECT statements." ); - } - stmt.addBatch( query ); - } - int[] result = stmt.executeBatch(); - polyconnection.commitTransaction(); - return result; - - } catch ( SQLException e ) { - throw translateSQLException( e ); - } catch ( Exception e ) { - polyconnection.rollbackTransaction(); - throw new RuntimeException( "SQL batch execution failed. Transaction was rolled back: " + e.getMessage(), e ); + try { + polyconnection.beginTransaction(); + try ( Statement stmt = polyconnection.getConnection().createStatement() ) { + for ( String query : queries ) { + String first = query.trim().toUpperCase(); + if ( first.startsWith( "SELECT" ) ) { + throw new UnsupportedOperationException( "Batch execution does not support SELECT statements." ); } - - } catch ( SQLException e ) { - throw new RuntimeException( "Failed to manage transaction", e ); + stmt.addBatch( query ); } + int[] resultArray = stmt.executeBatch(); + polyconnection.commitTransaction(); - case "mongo": - throw new UnsupportedOperationException( "MongoQL batch execution not yet implemented." ); + List result = new ArrayList<>( resultArray.length ); + for ( int r : resultArray ) { + result.add( r ); + } + return result; - case "cypher": - throw new UnsupportedOperationException( "Cypher batch execution not yet implemented." ); + } catch ( SQLException e ) { + polyconnection.rollbackTransaction(); + throw translateSQLException( e ); + } catch ( Exception e ) { + polyconnection.rollbackTransaction(); + throw new RuntimeException( + "SQL batch execution failed. Transaction was rolled back: " + e.getMessage(), e + ); + } + } catch ( SQLException e ) { + throw new RuntimeException( "Failed to manage transaction", e ); } + } + + /** + * @Description + * This function is capable of executing a List of MongoQL statements in one single Matlab-Java crossing. + * Each query is executed individually via the execute(...) method. The result of each query will be a List + * containing the JSON-encoded documents or scalars (as JSON strings). All individual query results are then grouped + * into an outer List, which represents the batch result. + * + * @param namespace The Mongo namespace (e.g. database / collection context). + * @param queries The list of Mongo query strings to be executed. + * @return List> result An outer list with one entry per query. Each entry is a List containing + * the documents or scalar results (as JSON strings) returned by the respective query. + */ + public List> executeBatchMongo( String namespace, List queries ) { + polyconnection.openIfNeeded(); + List> results = new ArrayList<>(); + for ( String query : queries ) { + @SuppressWarnings("unchecked") List res = (List) execute( "mongo", namespace, query ); + results.add( res ); + } + return results; } @@ -242,145 +263,113 @@ public Object SQLResultToMatlab( ResultSet rs ) throws Exception { } - private String[] DocumentToMatlab( DocumentResult documentResult ) { + private List DocumentToMatlab( DocumentResult documentResult ) { List docs = new ArrayList<>(); - Iterator documentIterator = documentResult.iterator(); // Call iterator for PolyDocumentResult + Iterator documentIterator = documentResult.iterator(); while ( documentIterator.hasNext() ) { PolyDocument document = documentIterator.next(); docs.add( NestedPolyDocumentToString( document ) ); } - return docs.toArray( new String[0] ); + return docs; } private String NestedPolyDocumentToString( PolyDocument document ) { - StringBuilder stringbuilder = new StringBuilder(); - stringbuilder.append( "{" ); + StringBuilder sb = new StringBuilder(); + sb.append( "{" ); + Iterator> it = document.entrySet().iterator(); + while ( it.hasNext() ) { + Map.Entry entry = it.next(); + sb.append( "\"" ).append( entry.getKey() ).append( "\":" ); + sb.append( anyToJson( entry.getValue() ) ); + if ( it.hasNext() ) + sb.append( "," ); + } + sb.append( "}" ); + return sb.toString(); + } - Iterator> iterator = document.entrySet().iterator(); - while ( iterator.hasNext() ) { - Map.Entry entry = iterator.next(); - String key = entry.getKey().toString(); - TypedValue value = entry.getValue(); - stringbuilder.append( "\"" ).append( key ).append( "\":" ); + private String NestedListToString( Array array ) { + StringBuilder sb = new StringBuilder(); + sb.append( "[" ); + try { + Object[] elems = (Object[]) array.getArray(); + for ( int i = 0; i < elems.length; i++ ) { + sb.append( anyToJson( elems[i] ) ); + if ( i < elems.length - 1 ) + sb.append( "," ); + } + } catch ( SQLException ex ) { + throw new RuntimeException( "List serialization error: " + ex.getMessage(), ex ); + } + sb.append( "]" ); + return sb.toString(); + } + - try { + private String anyToJson( Object result ) { + if ( result == null ) { + return "null"; + } + try { + if ( result instanceof TypedValue ) { + TypedValue value = (TypedValue) result; switch ( value.getValueCase() ) { case DOCUMENT: - stringbuilder.append( NestedPolyDocumentToString( value.asDocument() ) ); - break; + return NestedPolyDocumentToString( value.asDocument() ); case LIST: - stringbuilder.append( NestedListToString( value.asArray() ) ); - break; + return NestedListToString( value.asArray() ); case BOOLEAN: - stringbuilder.append( value.asBoolean() ); - break; + return String.valueOf( value.asBoolean() ); case INTEGER: - stringbuilder.append( value.asInt() ); - break; + return String.valueOf( value.asInt() ); case LONG: - stringbuilder.append( value.asLong() ); - break; + return String.valueOf( value.asLong() ); case DOUBLE: - stringbuilder.append( value.asDouble() ); - break; + return String.valueOf( value.asDouble() ); case FLOAT: - stringbuilder.append( value.asFloat() ); - break; + return String.valueOf( value.asFloat() ); case BIG_DECIMAL: - stringbuilder.append( value.asBigDecimal().toPlainString() ); // toPlainString() turns 1E-7 into 0.0000001 - break; + return value.asBigDecimal().toPlainString(); case STRING: - stringbuilder.append( "\"" ).append( escapeJson( value.asString() ) ).append( "\"" ); - break; + return "\"" + escapeJson( value.asString() ) + "\""; case DATE: - stringbuilder.append( "\"" ).append( value.asDate().toString() ).append( "\"" ); - break; + return "\"" + value.asDate().toString() + "\""; case TIME: - stringbuilder.append( "\"" ).append( value.asTime().toString() ).append( "\"" ); - break; + return "\"" + value.asTime().toString() + "\""; case TIMESTAMP: - stringbuilder.append( "\"" ).append( value.asTimestamp().toString() ).append( "\"" ); - break; + return "\"" + value.asTimestamp().toString() + "\""; case INTERVAL: - stringbuilder.append( "\"" ).append( value.asInterval().toString() ).append( "\"" ); - break; + return "\"" + value.asInterval().toString() + "\""; case BINARY: - stringbuilder.append( "\"" ).append( Base64.getEncoder().encodeToString( value.asBytes() ) ).append( "\"" ); - break; + return "\"" + Base64.getEncoder().encodeToString( value.asBytes() ) + "\""; case FILE: - stringbuilder.append( "\"" ).append( escapeJson( String.valueOf( value.asBlob() ) ) ).append( "\"" ); - break; + return "\"" + escapeJson( String.valueOf( value.asBlob() ) ) + "\""; case NULL: - stringbuilder.append( "null" ); - break; + return "null"; default: - stringbuilder.append( "\"" ).append( escapeJson( value.toString() ) ).append( "\"" ); - } - } catch ( SQLException e ) { - throw new RuntimeException( "TypedValue error: " + e.getMessage(), e ); - } - - if ( iterator.hasNext() ) { - stringbuilder.append( "," ); - } - } - - stringbuilder.append( "}" ); - return stringbuilder.toString(); - } - - - private String NestedListToString( Array array ) { - StringBuilder sb = new StringBuilder(); - sb.append( "[" ); - - try { - Object[] elems = (Object[]) array.getArray(); - for ( int i = 0; i < elems.length; i++ ) { - Object e = elems[i]; - - if ( e instanceof PolyDocument ) { - sb.append( NestedPolyDocumentToString( (PolyDocument) e ) ); - } else if ( e instanceof Array ) { - sb.append( NestedListToString( (Array) e ) ); - } else if ( e instanceof Boolean ) { - sb.append( (Boolean) e ); - } else if ( e instanceof Integer ) { - sb.append( (Integer) e ); - } else if ( e instanceof Long ) { - sb.append( (Long) e ); - } else if ( e instanceof Double ) { - sb.append( (Double) e ); - } else if ( e instanceof Float ) { - sb.append( (Float) e ); - } else if ( e instanceof BigDecimal ) { - sb.append( ((BigDecimal) e).toPlainString() ); - } else if ( e instanceof String ) { - sb.append( "\"" ).append( escapeJson( (String) e ) ).append( "\"" ); - } else if ( e instanceof java.sql.Date - || e instanceof java.sql.Time - || e instanceof java.sql.Timestamp ) { - sb.append( "\"" ).append( e.toString() ).append( "\"" ); - } else if ( e instanceof byte[] ) { - sb.append( "\"" ).append( Base64.getEncoder().encodeToString( (byte[]) e ) ).append( "\"" ); - } else if ( e == null ) { - sb.append( "null" ); - } else { - sb.append( "\"" ).append( escapeJson( e.toString() ) ).append( "\"" ); - } - - if ( i < elems.length - 1 ) { - sb.append( "," ); + return "\"" + escapeJson( value.toString() ) + "\""; } + } else if ( result instanceof PolyDocument ) { + return NestedPolyDocumentToString( (PolyDocument) result ); + } else if ( result instanceof Array ) { + return NestedListToString( (Array) result ); + } else if ( result instanceof String ) { + return "\"" + escapeJson( (String) result ) + "\""; + } else if ( result instanceof java.sql.Date + || result instanceof java.sql.Time + || result instanceof java.sql.Timestamp ) { + return "\"" + result.toString() + "\""; + } else if ( result instanceof byte[] ) { + return "\"" + Base64.getEncoder().encodeToString( (byte[]) result ) + "\""; + } else { + // numbers, booleans, anything else + return String.valueOf( result ); } - } catch ( SQLException ex ) { - throw new RuntimeException( "List serialization error: " + ex.getMessage(), ex ); + } catch ( SQLException e ) { + throw new RuntimeException( "Serialization error: " + e.getMessage(), e ); } - - sb.append( "]" ); - return sb.toString(); } @@ -415,7 +404,7 @@ private RuntimeException translateSQLException( SQLException e ) { return new RuntimeException( "Syntax error in query: " + e.getMessage(), e ); } } - return new RuntimeException( "SQL execution failed: " + e.getMessage(), e ); + return new RuntimeException( "Query execution failed: " + e.getMessage(), e ); } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java index b3995b4..305fb88 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java @@ -3,6 +3,9 @@ import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; + public class QueryExecutorTestMQL { private static PolyphenyConnection myconnection; @@ -16,11 +19,9 @@ static void setUpNamespaceAndCollection() throws Exception { myconnection = new PolyphenyConnection( "localhost", 20590, "pa", "" ); myexecutor = new QueryExecutor( myconnection ); - // Trigger implicit creation by inserting a dummy doc try { myexecutor.execute( "mongo", "mongotest", "db.createCollection(\"unittest_collection\")" ); } catch ( Exception ignoredException ) { - } } @@ -55,67 +56,62 @@ void clearCollectionAfter() { @Test void testInsertandDrop() { - // Query to make sure the unittest_collection is actually empty Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - String[] docs = (String[]) result; - assertEquals( 0, docs.length ); + assertTrue( result instanceof List, "Expected a List" ); + List docs = (List) result; + assertEquals( 0, docs.size() ); - // Insert entry into database myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":14})" ); result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({\"age\":14})" ); - docs = (String[]) result; - assertEquals( 1, docs.length ); - assertTrue( docs[0].contains( "\"age\":14" ) ); + docs = (List) result; + assertEquals( 1, docs.size() ); + assertTrue( docs.get( 0 ).contains( "\"age\":14" ) ); - // Drop entry myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.deleteMany({})" ); result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({\"age\": {\"$eq\": 14}})" ); - docs = (String[]) result; - assertEquals( 0, docs.length ); + docs = (List) result; + assertEquals( 0, docs.size() ); } @Test void testInsertManyAndNestedDocument() { - // insert a document (namespace passed separately; query contains only collection.operation) - myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":14})" ); myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":20})" ); myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":24})" ); myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":30, \"adress\": {\"Country\": \"Switzerland\", \"Code\": 4051}})" ); Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({\"age\": {$gt:29}})" ); - assertTrue( result instanceof String[], "expected a String[] for DocumentResult" ); - String[] docs = (String[]) result; // you can also do this quicker by String[] docs = (String[]) myexecuter.execute(...) - assertEquals( 1, docs.length ); - //System.out.println( docs[0] ); - assertTrue( docs[0].contains( "\"age\":30" ) ); + assertTrue( result instanceof List, "expected a List for DocumentResult" ); + List docs = (List) result; + assertEquals( 1, docs.size() ); + assertTrue( docs.get( 0 ).contains( "\"age\":30" ) ); } @Test void testBooleanField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"flag\":true})" ); - String[] docs = (String[]) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - assertTrue( docs[0].contains( "\"flag\":true" ) ); + List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + assertTrue( docs.get( 0 ).contains( "\"flag\":true" ) ); } @Test void testIntegerAgeField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":42})" ); - String[] docs = (String[]) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - assertEquals( 1, docs.length ); - assertTrue( docs[0].contains( "\"age\":42" ) ); + List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + assertEquals( 1, docs.size() ); + assertTrue( docs.get( 0 ).contains( "\"age\":42" ) ); } @Test void testStringField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"name\":\"Alice\"})" ); - String[] docs = (String[]) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - assertEquals( 1, docs.length ); - assertTrue( docs[0].contains( "\"name\":\"Alice\"" ) ); + List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + assertEquals( 1, docs.size() ); + assertTrue( docs.get( 0 ).contains( "\"name\":\"Alice\"" ) ); } @@ -123,9 +119,9 @@ void testStringField() { void testLongField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"big\":1111111111111111111})" ); Object r = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - String[] docs = (String[]) r; - assertEquals( 1, docs.length ); - assertTrue( docs[0].contains( "\"big\":1111111111111111111" ) ); + List docs = (List) r; + assertEquals( 1, docs.size() ); + assertTrue( docs.get( 0 ).contains( "\"big\":1111111111111111111" ) ); } @@ -133,24 +129,24 @@ void testLongField() { void testDoubleField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"pi\":3.14159})" ); Object r = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - String[] docs = (String[]) r; - assertEquals( 1, docs.length ); - assertTrue( docs[0].contains( "\"pi\":3.14159" ) ); + List docs = (List) r; + assertEquals( 1, docs.size() ); + assertTrue( docs.get( 0 ).contains( "\"pi\":3.14159" ) ); } - @Disabled @Test - void testCountDocumentsReturnsScalar() { - // ensure empty, insert one then count - myexecutor.execute( "mongo", "unittest_collection", - "unittest_namespace.unittest_coll.insertOne({\"name\":\"Bob\"})" ); + void testCountDocumentsReturnsStringArray() { + myexecutor.execute( "mongo", "mongotest", + "db.unittest_collection.insertOne({\"name\":\"Bob\"})" ); - Object result = myexecutor.execute( "mongo", "unittest_namespace", - "unittest_namespace.unittest_coll.countDocuments({})" ); + Object result = myexecutor.execute( "mongo", "mongotest", + "db.unittest_collection.countDocuments({})" ); - assertTrue( result instanceof Number, "Expected numeric scalar from countDocuments" ); - assertEquals( 1, ((Number) result).intValue() ); + assertTrue( result instanceof List, "result should be a List" ); + List docs = (List) result; + assertEquals( 1, docs.size() ); + assertEquals( "{\"count\":1}", docs.get( 0 ) ); } @@ -158,52 +154,86 @@ void testCountDocumentsReturnsScalar() { void testListElementClasses() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"mixed\":[{\"bar\":2},1,\"foo\"]})" ); Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - String[] docs = (String[]) result; - System.out.println( docs[0] ); + List docs = (List) result; + System.out.println( docs.get( 0 ) ); } - @Disabled @Test void testArrayField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"scores\":[1,2,3]})" ); Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - String[] docs = (String[]) result; - assertEquals( 1, docs.length ); - assertTrue( docs[0].contains( "\"scores\":[1,2,3]" ) ); + List docs = (List) result; + assertEquals( 1, docs.size() ); + assertTrue( docs.get( 0 ).contains( "\"scores\":[1,2,3]" ) ); } - @Disabled @Test void testFindOnEmptyCollectionReturnsEmptyArray() { - // ensure empty - myexecutor.execute( "mongo", "unittest_namespace", - "unittest_namespace.unittest_coll.deleteMany({})" ); + Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + assertTrue( result instanceof List, "expected List even for empty cursor" ); + List docs = (List) result; + assertEquals( 0, docs.size() ); + } - Object out = myexecutor.execute( "mongo", "unittest_namespace", - "unittest_namespace.unittest_coll.find({})" ); - assertTrue( out instanceof String[], "expected String[] even for empty cursor" ); - String[] docs = (String[]) out; - assertEquals( 0, docs.length ); + @Test + void testInsertManyAndFindMultiple() { + myexecutor.execute( "mongo", "mongotest", + "db.unittest_collection.insertOne({\"id\":10,\"name\":\"A\"})" ); + myexecutor.execute( "mongo", "mongotest", + "db.unittest_collection.insertOne({\"id\":11,\"name\":\"B\"})" ); + + Object out = myexecutor.execute( "mongo", "mongotest", + "db.unittest_collection.find({})" ); + + assertTrue( out instanceof List ); + List docs = (List) out; + assertEquals( 2, docs.size() ); } - @Disabled @Test - void testInsertManyAndFindMultiple() { - myexecutor.execute( "mongo", "unittest_namespace", - "unittest_namespace.unittest_coll.insertOne({\"_id\":10,\"name\":\"A\"})" ); - myexecutor.execute( "mongo", "unittest_namespace", - "unittest_namespace.unittest_coll.insertOne({\"_id\":11,\"name\":\"B\"})" ); + void testBatchInsertAndFind() { + List queries = new ArrayList<>(); + queries.add( "db.unittest_collection.insertOne({\"name\":\"Alice\",\"age\":25})" ); + queries.add( "db.unittest_collection.insertOne({\"name\":\"Bob\",\"age\":30})" ); + + Object out = myexecutor.executeBatchMongo( "mongotest", queries ); + + assertTrue( out instanceof List, "Expected a List of results" ); + @SuppressWarnings("unchecked") List> results = (List>) out; + + assertEquals( 2, results.size(), "Expected two results (one per insert)" ); + assertTrue( results.get( 0 ) instanceof List, "First insert result should be a List" ); + assertTrue( results.get( 1 ) instanceof List, "Second insert result should be a List" ); + assertEquals( 1, results.get( 0 ).size(), "Each insert should yield a singleton list" ); + assertEquals( 1, results.get( 1 ).size(), "Each insert should yield a singleton list" ); + } + + + @Test + void testBatchMixedOps() { + List queries = new ArrayList<>(); + queries.add( "db.unittest_collection.insertOne({\"name\":\"Charlie\",\"active\":true})" ); + queries.add( "db.unittest_collection.countDocuments({})" ); + + Object out = myexecutor.executeBatchMongo( "mongotest", queries ); + + assertTrue( out instanceof List, "Expected a List of results" ); + @SuppressWarnings("unchecked") List> results = (List>) out; + + assertEquals( 2, results.size(), "Expected two results" ); + assertTrue( results.get( 0 ) instanceof List, "First result should be a List" ); + assertTrue( results.get( 1 ) instanceof List, "Second result should be a List" ); - Object out = myexecutor.execute( "mongo", "unittest_namespace", - "unittest_namespace.unittest_coll.find({})" ); + // Insert → singleton list with ack/JSON + assertEquals( 1, results.get( 0 ).size(), "Insert should yield a singleton list" ); - assertTrue( out instanceof String[] ); - String[] docs = (String[]) out; - assertEquals( 2, docs.length ); + // Count → singleton list with a number string + assertEquals( 1, results.get( 1 ).size(), "Count should yield a singleton list" ); + assertTrue( results.get( 1 ).get( 0 ).contains( "1" ), "Count result should include '1'" ); } } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java index 93010ee..94cd769 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java @@ -244,13 +244,13 @@ void testBatchInsertEmployees() { ); // Do the batch execution using executeBatch(...) - int[] counts = myexecutor.executeBatch( "sql", "unittest_namespace", queries ); + List counts = myexecutor.executeBatchSql( queries ); // Test that the lenghth of the counts vector is 13 (for 13 queries in the queries liest). - assertEquals( 13, counts.length, "Batch should return 13 results" ); + assertEquals( 13, counts.size(), "Batch should return 13 results" ); // Test the i-th entry in the counts vector is actually 1 (because the i-th query changed exactly 1 row) - for ( int c : counts ) { + for ( Object c : counts ) { assertEquals( 1, c, "Each INSERT should affect exactly 1 row" ); } @@ -275,7 +275,7 @@ void testBatchRollbackOnFailure() { // Run the ill posed batch query and test an exception is thrown. assertThrows( RuntimeException.class, () -> { - myexecutor.executeBatch( "sql", "unittest_namespace", queries ); + myexecutor.executeBatchSql( queries ); } ); // Query the whole table to make sure it is really empty. @@ -315,7 +315,7 @@ void testCommitFailureRollback() { ); assertThrows( RuntimeException.class, () -> { - myexecutor.executeBatch( "sql", "unittest_namespace", queries ); + myexecutor.executeBatchSql( queries ); } ); Object result = myexecutor.execute( "sql", "unittest_namespace", "SELECT * FROM unittest_namespace.batch_table" ); From b848bc7b955fd7a4813236febac3268906dc2f4e Mon Sep 17 00:00:00 2001 From: fygo97 Date: Fri, 3 Oct 2025 17:48:17 +0200 Subject: [PATCH 35/44] Modified some documentation in the JUnit tests and the QueryExecutor.java --- .vscode/settings.json | 17 +++++ .../polyphenyconnector/QueryExecutor.java | 50 ++++++------ .../QueryExecutorTestMQL.java | 76 ++++++++++++++----- .../QueryExecutorTestSQL.java | 8 +- 4 files changed, 98 insertions(+), 53 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9462d61..282e471 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,18 +6,35 @@ "cSpell.words": [ "Afterall", "disp", + "elems", "empid", "emps", + "HASPOLYPHENY", + "Insertand", + "iscell", + "ischar", + "isscalar", + "istable", "JDBC", "johnrengelman", + "Matlabtoolbox", "mongoql", + "mongotest", + "mtlbx", + "Multimodel", + "multiquery", "myexecutor", + "mypath", "Polpyheny's", "polyconnection", "Polypheny", "polyphenyconnector", + "POLYWRAPPER", + "QUERYLIST", + "QUERYSTR", "stringbuilder", "varargin", + "VARCHAR", "wrongpass", "wronguser" ], diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 292b35b..11f7c3b 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -18,7 +18,6 @@ public class QueryExecutor { * * @param polyconnection: PolyphenyConnection object that holds the connection * details to the Database. It's used to execute queries - * @return: Object of the query (SQL: empty, scalar or table; MongoQL: TODO; Cypher: TODO) **/ public QueryExecutor( PolyphenyConnection polyconnection ) { this.polyconnection = polyconnection; @@ -29,20 +28,7 @@ public QueryExecutor( PolyphenyConnection polyconnection ) { * @Description * - Executes the query depending on the language given by the user * - * @param language: The database language that is used (e.g. SQL, MongoQL,Cypher) - * @param namespace: The namespace in the query string. For SQL this argument as no effect as we use JDBC's - * executeQuery(...)/executeUpdate(...). For MQL this argument will be passed to JDBC's execute(...) function. For further - * information consult Polpyheny's web documentation. - * @param query: The query-text to be executed (e.g. FROM emps SELECT *) - * - * @return: ResultToMatlab(rs) which is a Matlab compatible object that is cast to the Matlab user. - **/ - - /** - * @Description - * - Executes the query depending on the language given by the user - * - * @param language: The database language that is used (e.g. SQL, MongoQL,Cypher) + * @param language: The database language that is used (e.g. SQL, Mongo,Cypher) * @param namespace: The namespace in the query string. For SQL this argument as no effect as we use JDBC's * executeQuery(...)/executeUpdate(...). For MQL this argument will be passed to JDBC's execute(...) function. For further * information consult Polpyheny's web documentation. @@ -56,7 +42,7 @@ public Object execute( String language, String namespace, String query ) { Connection conn = polyconnection.getConnection(); conn.setAutoCommit( true ); } catch ( SQLException e ) { - throw translateSQLException( e ); + throw translateException( e ); } switch ( language.toLowerCase() ) { @@ -82,7 +68,7 @@ public Object execute( String language, String namespace, String query ) { } } catch ( SQLException e ) { - throw translateSQLException( e ); + throw translateException( e ); } catch ( Exception e ) { throw new RuntimeException( "SQL execution failed: " + e.getMessage(), e ); } @@ -105,10 +91,13 @@ public Object execute( String language, String namespace, String query ) { switch ( result.getResultType() ) { case DOCUMENT: - // Always returns a List + // Unwrapping according to → https://docs.polypheny.com/en/latest/drivers/jdbc/extensions/result DocumentResult documentResult = result.unwrap( DocumentResult.class ); return DocumentToMatlab( documentResult ); + // This case was never used in any of the JUnit Tests, as every Mongo Query currently seems to be wrapped as + // PolyDocument by the JDBC Driver. The case was still left in as security based on the official documentation: + // → https://docs.polypheny.com/en/latest/drivers/jdbc/extensions/result case SCALAR: ScalarResult scalarResult = result.unwrap( ScalarResult.class ); long scalar = scalarResult.getScalar(); @@ -118,7 +107,7 @@ public Object execute( String language, String namespace, String query ) { throw new UnsupportedOperationException( "Unhandled result type: " + result.getResultType() ); } } catch ( SQLException e ) { - throw translateSQLException( e ); + throw translateException( e ); } catch ( Exception e ) { throw new RuntimeException( "Mongo execution failed: " + e.getMessage(), e ); } @@ -138,7 +127,8 @@ public Object execute( String query ) { /** * @Description * This function is capable of executing a List of non-SELECT SQL statements in one single Matlab-Java crossing. - * All SQL statements except SELECT are supported. For further information consult the Polypheny JDBC Driver documentation. + * All SQL statements except SELECT are supported. For further information consult the Polypheny JDBC Driver documentation + * → https://docs.polypheny.com/en/latest/drivers/jdbc/relational/statement * * @param queries The list of SQL query strings to be executed. * @return List result A list of integers, where the i-th entry will denote for the i-th query how many rows were touched, e.g. @@ -167,7 +157,7 @@ public List executeBatchSql( List queries ) { } catch ( SQLException e ) { polyconnection.rollbackTransaction(); - throw translateSQLException( e ); + throw translateException( e ); } catch ( Exception e ) { polyconnection.rollbackTransaction(); throw new RuntimeException( @@ -182,7 +172,7 @@ public List executeBatchSql( List queries ) { /** * @Description - * This function is capable of executing a List of MongoQL statements in one single Matlab-Java crossing. + * This function is capable of executing a List of Mongo statements in one single Matlab-Java crossing. * Each query is executed individually via the execute(...) method. The result of each query will be a List * containing the JSON-encoded documents or scalars (as JSON strings). All individual query results are then grouped * into an outer List, which represents the batch result. @@ -394,7 +384,7 @@ private static String escapeJson( String string ) { * @param e The exception caught. 08 denotes an error with the Polypheny connection, 42 denotes a * @return */ - private RuntimeException translateSQLException( SQLException e ) { + private RuntimeException translateException( SQLException e ) { String state = e.getSQLState(); if ( state != null ) { if ( state.startsWith( "08" ) ) { @@ -407,7 +397,9 @@ private RuntimeException translateSQLException( SQLException e ) { return new RuntimeException( "Query execution failed: " + e.getMessage(), e ); } - + /* + * This function might be used in the future to automatically detect the namespace in MQL queries. + private static void checkNamespace( String language, String namespace ) { if ( namespace == null || namespace.isEmpty() ) { if ( language.equalsIgnoreCase( "sql" ) ) { @@ -419,18 +411,19 @@ private static void checkNamespace( String language, String namespace ) { } } } - + */ /** * @Description - * This function determines the operation (e.g. "find" or "insertOne") of a MongoQL query. This is important to distinguish + * This function determines the operation (e.g. "find" or "insertOne") of a Mongo query. This is important to distinguish * whether to use executeUpdate or executeQuery. Functionality was moved to a function (instead of handling it like for SQL) - * because in MongoQL queries the operation isn't as easy to determine. + * because in Mongo queries the operation isn't as easy to determine. * MQL queries are always of the form ..() * * @param q The query text of type String * @return - */ + **/ + /* private static String extractMongoOperation( String q ) { String query = q.trim(); int paren = query.indexOf( '(' ); // get the position of the first "(" in the query. @@ -444,5 +437,6 @@ private static String extractMongoOperation( String q ) { String operation = query.substring( lastDot + 1, paren ).trim(); return operation; // return the } + */ } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java index 305fb88..633e8d3 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java @@ -54,6 +54,7 @@ void clearCollectionAfter() { } + @SuppressWarnings("unchecked") @Test void testInsertandDrop() { Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); @@ -63,6 +64,7 @@ void testInsertandDrop() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":14})" ); result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({\"age\":14})" ); + docs = (List) result; assertEquals( 1, docs.size() ); assertTrue( docs.get( 0 ).contains( "\"age\":14" ) ); @@ -83,7 +85,7 @@ void testInsertManyAndNestedDocument() { Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({\"age\": {$gt:29}})" ); assertTrue( result instanceof List, "expected a List for DocumentResult" ); - List docs = (List) result; + @SuppressWarnings("unchecked") List docs = (List) result; assertEquals( 1, docs.size() ); assertTrue( docs.get( 0 ).contains( "\"age\":30" ) ); } @@ -92,7 +94,7 @@ void testInsertManyAndNestedDocument() { @Test void testBooleanField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"flag\":true})" ); - List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + @SuppressWarnings("unchecked") List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); assertTrue( docs.get( 0 ).contains( "\"flag\":true" ) ); } @@ -100,7 +102,7 @@ void testBooleanField() { @Test void testIntegerAgeField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"age\":42})" ); - List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + @SuppressWarnings("unchecked") List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); assertEquals( 1, docs.size() ); assertTrue( docs.get( 0 ).contains( "\"age\":42" ) ); } @@ -109,7 +111,7 @@ void testIntegerAgeField() { @Test void testStringField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"name\":\"Alice\"})" ); - List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + @SuppressWarnings("unchecked") List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); assertEquals( 1, docs.size() ); assertTrue( docs.get( 0 ).contains( "\"name\":\"Alice\"" ) ); } @@ -119,7 +121,7 @@ void testStringField() { void testLongField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"big\":1111111111111111111})" ); Object r = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - List docs = (List) r; + @SuppressWarnings("unchecked") List docs = (List) r; assertEquals( 1, docs.size() ); assertTrue( docs.get( 0 ).contains( "\"big\":1111111111111111111" ) ); } @@ -129,7 +131,7 @@ void testLongField() { void testDoubleField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"pi\":3.14159})" ); Object r = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - List docs = (List) r; + @SuppressWarnings("unchecked") List docs = (List) r; assertEquals( 1, docs.size() ); assertTrue( docs.get( 0 ).contains( "\"pi\":3.14159" ) ); } @@ -144,7 +146,7 @@ void testCountDocumentsReturnsStringArray() { "db.unittest_collection.countDocuments({})" ); assertTrue( result instanceof List, "result should be a List" ); - List docs = (List) result; + @SuppressWarnings("unchecked") List docs = (List) result; assertEquals( 1, docs.size() ); assertEquals( "{\"count\":1}", docs.get( 0 ) ); } @@ -154,7 +156,7 @@ void testCountDocumentsReturnsStringArray() { void testListElementClasses() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"mixed\":[{\"bar\":2},1,\"foo\"]})" ); Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - List docs = (List) result; + @SuppressWarnings("unchecked") List docs = (List) result; System.out.println( docs.get( 0 ) ); } @@ -163,12 +165,13 @@ void testListElementClasses() { void testArrayField() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"scores\":[1,2,3]})" ); Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - List docs = (List) result; + @SuppressWarnings("unchecked") List docs = (List) result; assertEquals( 1, docs.size() ); assertTrue( docs.get( 0 ).contains( "\"scores\":[1,2,3]" ) ); } + @SuppressWarnings("unchecked") @Test void testFindOnEmptyCollectionReturnsEmptyArray() { Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); @@ -185,11 +188,11 @@ void testInsertManyAndFindMultiple() { myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"id\":11,\"name\":\"B\"})" ); - Object out = myexecutor.execute( "mongo", "mongotest", + Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); - assertTrue( out instanceof List ); - List docs = (List) out; + assertTrue( result instanceof List ); + @SuppressWarnings("unchecked") List docs = (List) result; assertEquals( 2, docs.size() ); } @@ -200,10 +203,10 @@ void testBatchInsertAndFind() { queries.add( "db.unittest_collection.insertOne({\"name\":\"Alice\",\"age\":25})" ); queries.add( "db.unittest_collection.insertOne({\"name\":\"Bob\",\"age\":30})" ); - Object out = myexecutor.executeBatchMongo( "mongotest", queries ); + Object result = myexecutor.executeBatchMongo( "mongotest", queries ); - assertTrue( out instanceof List, "Expected a List of results" ); - @SuppressWarnings("unchecked") List> results = (List>) out; + assertTrue( result instanceof List, "Expected a List of results" ); + @SuppressWarnings("unchecked") List> results = (List>) result; assertEquals( 2, results.size(), "Expected two results (one per insert)" ); assertTrue( results.get( 0 ) instanceof List, "First insert result should be a List" ); @@ -219,21 +222,52 @@ void testBatchMixedOps() { queries.add( "db.unittest_collection.insertOne({\"name\":\"Charlie\",\"active\":true})" ); queries.add( "db.unittest_collection.countDocuments({})" ); - Object out = myexecutor.executeBatchMongo( "mongotest", queries ); + Object result = myexecutor.executeBatchMongo( "mongotest", queries ); - assertTrue( out instanceof List, "Expected a List of results" ); - @SuppressWarnings("unchecked") List> results = (List>) out; + assertTrue( result instanceof List, "Expected a List of results" ); + @SuppressWarnings("unchecked") List> results = (List>) result; assertEquals( 2, results.size(), "Expected two results" ); assertTrue( results.get( 0 ) instanceof List, "First result should be a List" ); assertTrue( results.get( 1 ) instanceof List, "Second result should be a List" ); - // Insert → singleton list with ack/JSON - assertEquals( 1, results.get( 0 ).size(), "Insert should yield a singleton list" ); - // Count → singleton list with a number string + assertEquals( 1, results.get( 0 ).size(), "Insert should yield a singleton list" ); assertEquals( 1, results.get( 1 ).size(), "Count should yield a singleton list" ); + + // Content → Check results assertTrue( results.get( 1 ).get( 0 ).contains( "1" ), "Count result should include '1'" ); } + + @Test + void testSyntaxErrorThrows() { + // Missing closing brace makes this invalid JSON + String badQuery = "db.unittest_collection.insertOne({\"foo\":123)"; // typo + + RuntimeException runtimeException = assertThrows( RuntimeException.class, () -> { + myexecutor.execute( "mongo", "unittest_namespace", badQuery ); + } ); + assertTrue( runtimeException.getMessage().contains( "Syntax error" ) || + runtimeException.getMessage().contains( "execution failed" ), + "Exception message should indicate syntax error" ); + + assertThrows( Exception.class, () -> { + myexecutor.execute( "mongo", "mongotest", badQuery ); + } ); + } + + + @Test + void testMultiStatementMongoFails() { + String illegal_multiquery = "" + + "db.people.insertOne({\"name\":\"Alice\",\"age\":20}); " + + "db.people.insertOne({\"name\":\"Bob\",\"age\":24}); " + + "db.people.find({})"; + + assertThrows( Exception.class, () -> { + myexecutor.execute( "mongo", "mongotest", illegal_multiquery ); + }, "Polypheny should not support multi-statement MongoQL with ';'" ); + } + } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java index 94cd769..e9d0378 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java @@ -246,7 +246,7 @@ void testBatchInsertEmployees() { // Do the batch execution using executeBatch(...) List counts = myexecutor.executeBatchSql( queries ); - // Test that the lenghth of the counts vector is 13 (for 13 queries in the queries liest). + // Test that the length of the counts vector is 13 (for 13 queries in the queries list). assertEquals( 13, counts.size(), "Batch should return 13 results" ); // Test the i-th entry in the counts vector is actually 1 (because the i-th query changed exactly 1 row) @@ -298,11 +298,11 @@ void testConnectionFailure() { @Test void testSyntaxError() { - RuntimeException ex = assertThrows( RuntimeException.class, () -> { + RuntimeException runtimeException = assertThrows( RuntimeException.class, () -> { myexecutor.execute( "sql", "unittest_namespace", "SELEC WRONG FROM nowhere" ); // typo: SELEC } ); - assertTrue( ex.getMessage().contains( "Syntax error" ) || - ex.getMessage().contains( "execution failed" ), + assertTrue( runtimeException.getMessage().contains( "Syntax error" ) || + runtimeException.getMessage().contains( "execution failed" ), "Exception message should indicate syntax error" ); } From c9c620285917e7fef9e02336b58fd854c2581744 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Fri, 3 Oct 2025 18:31:20 +0200 Subject: [PATCH 36/44] This commit contains some changes to make the Batch Query safer incase the rollback within an exception fails. Before rollback failures would have masked the initial failure reason. Now both the initial failure reaons + rollback failures (if happened) will be caught and propagated to Matlab user. --- app/bin/main/Polypheny.m | 99 +++++++++++-------- app/src/main/java/Polypheny.m | 99 +++++++++++-------- .../polyphenyconnector/QueryExecutor.java | 28 ++++-- 3 files changed, 132 insertions(+), 94 deletions(-) diff --git a/app/bin/main/Polypheny.m b/app/bin/main/Polypheny.m index 22a0bed..527636b 100644 --- a/app/bin/main/Polypheny.m +++ b/app/bin/main/Polypheny.m @@ -11,73 +11,88 @@ function PolyWrapper = Polypheny( host, port, user, password ) % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor - % LANGUAGE: The database language ('sql', 'mongoql', 'cypher') + % LANGUAGE: The database language ('sql', 'mongo', 'cypher') % HOST: Database host (e.g. 'localhost') % PORT: Database port (integer) % USER: Username % PASSWORD: Password % This makes sure that Matlab sees Java classes supplied by the .jar files in the Matlabtoolbox PolyphenyConnector.mtlbx - if ~polypheny.Polypheny.hasPolypheny() - startup(); + try + if ~polypheny.Polypheny.hasPolypheny() + startup(); + end + PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); + PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + + catch ME %Matlab Exception + disp("Error: " + ME.message) end - PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); - PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + end function matlab_result = query( PolyWrapper, language, namespace, queryStr ) % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java - % POLYWRAPPER: The PolyWrapper Matlab object - % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher - % QUERYSTR: The queryStr set by the user - % @return T: The result of the query -> return type differs for SQL,MongoQl and Cypher - java_result = PolyWrapper.queryExecutor.execute(string(language), string(namespace), queryStr ); - - % SQL case - if language == "sql" - if isempty( java_result ) - matlab_result = []; - - elseif isscalar( java_result ) - matlab_result = java_result; - - elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 - matlab_result = cell(java_result); - colNames = cell(matlab_result{1}); - data = cell(matlab_result{2}); % Object[][] → MATLAB cell - matlab_result = cell2table(data, 'VariableNames', colNames); - - else - matlab_result = []; % fallback to avoid cell2table crash - end + % POLYWRAPPER: The PolyWrapper Matlab object + % LANGUAGE: The language of the query string -> SQL, mongo, Cypher + % QUERYSTR: The queryStr set by the user + % @return matlab_result: The result of the query -> return type differs for SQL,Mongo and Cypher - % MongoQL case - elseif language == "mongoql" - if isscalar( java_result) - matlab_result = java_result; + try + java_result = PolyWrapper.queryExecutor.execute(string(language), string(namespace), queryStr ); - elseif ischar( java_result ) || isstring (java_result) - matlab_result = string(java_result); + % SQL case + if language == "sql" + if isempty( java_result ) + matlab_result = []; - else - try - matlab_result = java_result - catch ME %Matlab Exception - disp("Error: " + ME.message) + elseif isscalar( java_result ) + matlab_result = java_result; + + elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 + matlab_result = cell(java_result); + colNames = cell(matlab_result{1}); + data = cell(matlab_result{2}); % Object[][] → MATLAB cell + matlab_result = cell2table(data, 'VariableNames', colNames); + + else + matlab_result = []; % fallback to avoid cell2table crash end - - end + % mongo case + elseif language == "mongo" + + if isscalar( java_result) + matlab_result = java_result; + + elseif ischar( java_result ) || isstring (java_result) + matlab_result = string(java_result); + + else + try + matlab_result = java_result + catch ME %Matlab Exception + disp("Error: " + ME.message) + end + + end + % mongo case + elseif language == "cypher" + + end + + catch ME %Matlab Exception + disp("Error: " + ME.message) end end function matlab_result = queryBatch( PolyWrapper, language, namespace, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements - % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) + % QUERYLIST: A cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) % % Returns: int array with rows affected per statement diff --git a/app/src/main/java/Polypheny.m b/app/src/main/java/Polypheny.m index 22a0bed..527636b 100644 --- a/app/src/main/java/Polypheny.m +++ b/app/src/main/java/Polypheny.m @@ -11,73 +11,88 @@ function PolyWrapper = Polypheny( host, port, user, password ) % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor - % LANGUAGE: The database language ('sql', 'mongoql', 'cypher') + % LANGUAGE: The database language ('sql', 'mongo', 'cypher') % HOST: Database host (e.g. 'localhost') % PORT: Database port (integer) % USER: Username % PASSWORD: Password % This makes sure that Matlab sees Java classes supplied by the .jar files in the Matlabtoolbox PolyphenyConnector.mtlbx - if ~polypheny.Polypheny.hasPolypheny() - startup(); + try + if ~polypheny.Polypheny.hasPolypheny() + startup(); + end + PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); + PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + + catch ME %Matlab Exception + disp("Error: " + ME.message) end - PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); - PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + end function matlab_result = query( PolyWrapper, language, namespace, queryStr ) % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java - % POLYWRAPPER: The PolyWrapper Matlab object - % LANGUAGE: The language of the query string -> SQL, MongoQL, Cypher - % QUERYSTR: The queryStr set by the user - % @return T: The result of the query -> return type differs for SQL,MongoQl and Cypher - java_result = PolyWrapper.queryExecutor.execute(string(language), string(namespace), queryStr ); - - % SQL case - if language == "sql" - if isempty( java_result ) - matlab_result = []; - - elseif isscalar( java_result ) - matlab_result = java_result; - - elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 - matlab_result = cell(java_result); - colNames = cell(matlab_result{1}); - data = cell(matlab_result{2}); % Object[][] → MATLAB cell - matlab_result = cell2table(data, 'VariableNames', colNames); - - else - matlab_result = []; % fallback to avoid cell2table crash - end + % POLYWRAPPER: The PolyWrapper Matlab object + % LANGUAGE: The language of the query string -> SQL, mongo, Cypher + % QUERYSTR: The queryStr set by the user + % @return matlab_result: The result of the query -> return type differs for SQL,Mongo and Cypher - % MongoQL case - elseif language == "mongoql" - if isscalar( java_result) - matlab_result = java_result; + try + java_result = PolyWrapper.queryExecutor.execute(string(language), string(namespace), queryStr ); - elseif ischar( java_result ) || isstring (java_result) - matlab_result = string(java_result); + % SQL case + if language == "sql" + if isempty( java_result ) + matlab_result = []; - else - try - matlab_result = java_result - catch ME %Matlab Exception - disp("Error: " + ME.message) + elseif isscalar( java_result ) + matlab_result = java_result; + + elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 + matlab_result = cell(java_result); + colNames = cell(matlab_result{1}); + data = cell(matlab_result{2}); % Object[][] → MATLAB cell + matlab_result = cell2table(data, 'VariableNames', colNames); + + else + matlab_result = []; % fallback to avoid cell2table crash end - - end + % mongo case + elseif language == "mongo" + + if isscalar( java_result) + matlab_result = java_result; + + elseif ischar( java_result ) || isstring (java_result) + matlab_result = string(java_result); + + else + try + matlab_result = java_result + catch ME %Matlab Exception + disp("Error: " + ME.message) + end + + end + % mongo case + elseif language == "cypher" + + end + + catch ME %Matlab Exception + disp("Error: " + ME.message) end end function matlab_result = queryBatch( PolyWrapper, language, namespace, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements - % QUERYLIST must be a cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) + % QUERYLIST: A cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) % % Returns: int array with rows affected per statement diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 11f7c3b..a32e87a 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -154,16 +154,24 @@ public List executeBatchSql( List queries ) { result.add( r ); } return result; - } catch ( SQLException e ) { - polyconnection.rollbackTransaction(); + try { + polyconnection.rollbackTransaction(); + } catch ( Exception rollbackException ) { + // Propagate both the SQL batch failure AND the rollback failure → User must be made aware + throw new RuntimeException( "SQL batch failed AND rollback failed: " + rollbackException.getMessage(), e ); + } throw translateException( e ); } catch ( Exception e ) { - polyconnection.rollbackTransaction(); - throw new RuntimeException( - "SQL batch execution failed. Transaction was rolled back: " + e.getMessage(), e - ); + try { + polyconnection.rollbackTransaction(); + } catch ( Exception rollbackEx ) { + // Propagate both the SQL batch failure AND the rollback failure → User must be made aware + throw new RuntimeException( "SQL batch failed AND rollback failed: " + rollbackEx.getMessage(), e ); + } + throw new RuntimeException( "SQL batch execution failed. Transaction was rolled back: " + e.getMessage(), e ); } + } catch ( SQLException e ) { throw new RuntimeException( "Failed to manage transaction", e ); } @@ -211,7 +219,7 @@ public Object SQLResultToMatlab( ResultSet rs ) throws Exception { // Case 1: Empty Result // ───────────────────────────── if ( !rs.next() ) { - System.out.println( "Empty result set." ); + //System.out.println( "Empty result set." ); return null; } @@ -219,7 +227,7 @@ public Object SQLResultToMatlab( ResultSet rs ) throws Exception { // Case 2: Scalar Result // ───────────────────────────── if ( colCount == 1 && rs.isLast() ) { - System.out.println( "Scalar result set." ); + //System.out.println( "Scalar result set." ); Object scalar = rs.getObject( 1 ); return scalar; } @@ -290,8 +298,8 @@ private String NestedListToString( Array array ) { if ( i < elems.length - 1 ) sb.append( "," ); } - } catch ( SQLException ex ) { - throw new RuntimeException( "List serialization error: " + ex.getMessage(), ex ); + } catch ( SQLException e ) { + throw new RuntimeException( "List serialization error: " + e.getMessage(), e ); } sb.append( "]" ); return sb.toString(); From e49c8ba79ac32146a326a41ceb36cc5c3efc7d10 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Fri, 3 Oct 2025 21:20:57 +0200 Subject: [PATCH 37/44] Tweaked the executeBatchMongo to communicate safely with the polypheny server. Modified the openIfNeeded to not set Autocommit to true. --- .../PolyphenyConnection.java | 8 ++- .../polyphenyconnector/QueryExecutor.java | 38 ++++++----- .../QueryExecutorTestMQL.java | 68 ++++++++++++++++++- 3 files changed, 93 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/polyphenyconnector/PolyphenyConnection.java b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java index a5dccb3..b2edf6d 100644 --- a/app/src/main/java/polyphenyconnector/PolyphenyConnection.java +++ b/app/src/main/java/polyphenyconnector/PolyphenyConnection.java @@ -43,7 +43,6 @@ public void openIfNeeded() { if ( connection == null ) { try { connection = DriverManager.getConnection( url, username, password ); - connection.setAutoCommit( true ); // make sure standard mode defaults to AutoCommit } catch ( SQLException e ) { throw new RuntimeException( "Failed to open connection", e ); } @@ -82,7 +81,7 @@ public void close() { connection.close(); } } catch ( SQLException e ) { - System.err.println( "Failed to close connection: " + e.getMessage() ); + throw new RuntimeException( "Failed to close connection: " + e.getMessage() ); } finally { connection = null; } @@ -140,4 +139,9 @@ public void rollbackTransaction() throws SQLException { connection.setAutoCommit( true ); } + + public void setAutoCommit( boolean AutoCommitMode ) throws SQLException { + connection.setAutoCommit( AutoCommitMode ); + } + } diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index a32e87a..4d65dd8 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -38,12 +38,6 @@ public QueryExecutor( PolyphenyConnection polyconnection ) { **/ public Object execute( String language, String namespace, String query ) { polyconnection.openIfNeeded(); - try { - Connection conn = polyconnection.getConnection(); - conn.setAutoCommit( true ); - } catch ( SQLException e ) { - throw translateException( e ); - } switch ( language.toLowerCase() ) { default: @@ -118,12 +112,6 @@ public Object execute( String language, String namespace, String query ) { } - // SQL convenience overload - public Object execute( String query ) { - return execute( "sql", "public", query ); - } - - /** * @Description * This function is capable of executing a List of non-SELECT SQL statements in one single Matlab-Java crossing. @@ -158,7 +146,7 @@ public List executeBatchSql( List queries ) { try { polyconnection.rollbackTransaction(); } catch ( Exception rollbackException ) { - // Propagate both the SQL batch failure AND the rollback failure → User must be made aware + // Propagate both the batch failure AND the rollback failure → User must be made throw new RuntimeException( "SQL batch failed AND rollback failed: " + rollbackException.getMessage(), e ); } throw translateException( e ); @@ -166,7 +154,7 @@ public List executeBatchSql( List queries ) { try { polyconnection.rollbackTransaction(); } catch ( Exception rollbackEx ) { - // Propagate both the SQL batch failure AND the rollback failure → User must be made aware + // Propagate both the batch failure AND the rollback failure → User must be made throw new RuntimeException( "SQL batch failed AND rollback failed: " + rollbackEx.getMessage(), e ); } throw new RuntimeException( "SQL batch execution failed. Transaction was rolled back: " + e.getMessage(), e ); @@ -193,11 +181,25 @@ public List executeBatchSql( List queries ) { public List> executeBatchMongo( String namespace, List queries ) { polyconnection.openIfNeeded(); List> results = new ArrayList<>(); - for ( String query : queries ) { - @SuppressWarnings("unchecked") List res = (List) execute( "mongo", namespace, query ); - results.add( res ); + try { + polyconnection.beginTransaction(); + + for ( String query : queries ) { + @SuppressWarnings("unchecked") List result = (List) execute( "mongo", namespace, query ); + results.add( result ); + } + + polyconnection.commitTransaction(); // commit if all succeeded + return results; + + } catch ( Exception e ) { + try { + polyconnection.rollbackTransaction(); // rollback if anything failed + } catch ( Exception rollbackEx ) { + throw new RuntimeException( "Rollback failed after batch error", rollbackEx ); + } + throw new RuntimeException( "Batch execution failed", e ); } - return results; } diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java index 633e8d3..497bb82 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java @@ -1,8 +1,12 @@ package polyphenyconnector; import org.junit.jupiter.api.*; +import org.polypheny.jdbc.PolyConnection; +import org.polypheny.jdbc.multimodel.*; + import static org.junit.jupiter.api.Assertions.*; +import java.sql.Connection; import java.util.ArrayList; import java.util.List; @@ -246,7 +250,7 @@ void testSyntaxErrorThrows() { String badQuery = "db.unittest_collection.insertOne({\"foo\":123)"; // typo RuntimeException runtimeException = assertThrows( RuntimeException.class, () -> { - myexecutor.execute( "mongo", "unittest_namespace", badQuery ); + myexecutor.execute( "mongo", "mongotest", badQuery ); } ); assertTrue( runtimeException.getMessage().contains( "Syntax error" ) || runtimeException.getMessage().contains( "execution failed" ), @@ -270,4 +274,66 @@ void testMultiStatementMongoFails() { }, "Polypheny should not support multi-statement MongoQL with ';'" ); } + + @Test + void testMongoRollbackSupport() throws Exception { + // Sanity check to verify rollback on Polypheny server side + myconnection.openIfNeeded(); + Connection conn = myconnection.getConnection(); + + try { + conn.setAutoCommit( false ); + PolyConnection polyConn = conn.unwrap( PolyConnection.class ); + PolyStatement stmt = polyConn.createPolyStatement(); + + // Insert Alice with id=1 + stmt.execute( "mongotest", "mongo", "db.mongotest.insertOne({\"id\": 1, \"name\": \"Alice\"})" ); + + // Verify Alice is visible before rollback + Result preRes = stmt.execute( "mongotest", "mongo", "db.mongotest.find({\"id\": 1})" ); + DocumentResult preDocs = preRes.unwrap( DocumentResult.class ); + assertTrue( preDocs.iterator().hasNext(), "Inserted document should be visible before rollback" ); + + // Roll back instead of commit + conn.rollback(); + + } finally { + conn.setAutoCommit( true ); + } + + // After rollback, Alice should not exist + PolyConnection polyConn = conn.unwrap( PolyConnection.class ); + PolyStatement ps = polyConn.createPolyStatement(); + Result res = ps.execute( "mongotest", "mongo", "db.mongotest.find({\"id\": 1})" ); + + DocumentResult docs = res.unwrap( DocumentResult.class ); + boolean hasDoc = docs.iterator().hasNext(); + + assertFalse( hasDoc, + "Rollback did not remove Mongo document with id=1" ); + } + + + @Test + void testExecuteBatchMongoRollback() { + myconnection.openIfNeeded(); + + // Prepare batch: 2 valid inserts + 1 faulty insert + List queries = new ArrayList<>(); + queries.add( "db.unittest_collection.insertOne({\"id\": 1, \"name\": \"Alice\"})" ); + queries.add( "db.unittest_collection.insertOne({\"id\": 2, \"name\": \"Bob\"})" ); + queries.add( "db.unittest_collection.insertOne({\"id\": 3, \"name\": \"Janice\"" ); // → missing closing }) brace + + // Expect the batch to throw (rollback triggered) + assertThrows( RuntimeException.class, () -> { + myexecutor.executeBatchMongo( "mongotest", queries ); + } ); + + // After rollback, none of the documents should exist + @SuppressWarnings("unchecked") List docs = (List) myexecutor.execute( + "mongo", "mongotest", "db.unittest_collection.find({\"id\": {\"$gte\": 0, \"$lte\": 100}})" ); + + assertEquals( 0, docs.size(), "Rollback should have undone all inserts when one failed" ); + } + } From 0051939ea141b4fba033b6f875a0b3db81acbe91 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 4 Oct 2025 21:11:42 +0200 Subject: [PATCH 38/44] Adapted the Matlabwrapper for the mongo case in query and queryBatch. All Matlabtests, JUnit Tests now pass. The project is ready for launch of the toolbox version 1.0. Minor changes were made to comments in the java side --- Polypheny_Connector.prj | 2 + app/bin/main/Polypheny.m | 139 +++++----- app/bin/main/PolyphenyWrapperTestMQL.m | 245 ++++++++++++++++++ app/bin/main/PolyphenyWrapperTestSQL.m | 219 ++++++++++++++++ app/src/main/java/Polypheny.m | 139 +++++----- app/src/main/java/PolyphenyWrapperTestMQL.m | 245 ++++++++++++++++++ app/src/main/java/PolyphenyWrapperTestSQL.m | 219 ++++++++++++++++ .../polyphenyconnector/QueryExecutor.java | 21 +- .../QueryExecutorTestMQL.java | 24 +- .../QueryExecutorTestSQL.java | 8 +- .../35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml | 2 + .../35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml | 2 + .../q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml | 2 + .../q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml | 2 + .../-7lw3W0RxTIVHffMmdu5nJIpdssd.xml | 6 + .../-7lw3W0RxTIVHffMmdu5nJIpdssp.xml | 2 + .../-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml | 2 + .../-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml | 2 + .../0D8h_eihs7G5arxD4hyQnS4bcwgd.xml | 2 + .../0D8h_eihs7G5arxD4hyQnS4bcwgp.xml | 2 + .../JX1bqBrXPl367ChfkEVPXC5BHlsd.xml | 6 + .../JX1bqBrXPl367ChfkEVPXC5BHlsp.xml | 2 + .../Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml | 6 + .../Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml | 2 + .../SWQui2F2vzpqtemNQS5lPS2aNsgd.xml | 2 + .../SWQui2F2vzpqtemNQS5lPS2aNsgp.xml | 2 + .../X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml | 2 + .../X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml | 2 + .../YIBBj8UPFRjwM860n51m88FAsPsd.xml | 2 + .../YIBBj8UPFRjwM860n51m88FAsPsp.xml | 2 + .../og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml | 2 + .../og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml | 2 + .../vAQG464ZlAetqjsF49GGb06XF3od.xml | 2 + .../vAQG464ZlAetqjsF49GGb06XF3op.xml | 2 + .../ycwBpgzd0N4JLCNuP64rppTLLz4d.xml | 2 + .../ycwBpgzd0N4JLCNuP64rppTLLz4p.xml | 2 + .../zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml | 6 + .../zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml | 2 + .../7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml | 2 + .../7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml | 2 + .../NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml | 2 + .../NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml | 2 + .../1pTU7g02qBsY8TI_s0SdV0qAGVId.xml | 2 + .../1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml | 2 + .../CblqjzyIIYW27RyXgKMjijiwmVEd.xml | 2 + .../CblqjzyIIYW27RyXgKMjijiwmVEp.xml | 2 + .../QY57lh1PuBv61TnyPsky3BtOVd8d.xml | 2 + .../QY57lh1PuBv61TnyPsky3BtOVd8p.xml | 2 + .../Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml | 6 + .../Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml | 2 + .../s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml | 2 + .../s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml | 2 + .../xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml | 2 + .../xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml | 2 + .../1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml | 2 + .../1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml | 2 + .../dpRIzUK051moAOA5OtMey_GCSjUd.xml | 2 + .../dpRIzUK051moAOA5OtMey_GCSjUp.xml | 2 + .../R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml | 2 + .../R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml | 2 + .../XMD_i55c50JsD1yXqTnVc5f2zzMd.xml | 2 + .../XMD_i55c50JsD1yXqTnVc5f2zzMp.xml | 2 + .../sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml | 2 + .../sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml | 2 + .../7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml | 2 + .../7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml | 2 + .../guZAfj9lbhvPokQd3ABhzUUeAcYd.xml | 2 + .../guZAfj9lbhvPokQd3ABhzUUeAcYp.xml | 2 + .../zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml | 2 + .../zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml | 2 + .../2kj09UetkV_lru3gvSPXnY6-nM4d.xml | 2 + .../2kj09UetkV_lru3gvSPXnY6-nM4p.xml | 2 + .../KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml | 2 + .../KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml | 2 + .../QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml | 2 + .../QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml | 2 + .../R1RggVhA72agIvELiuhWPRS8F0Id.xml | 2 + .../R1RggVhA72agIvELiuhWPRS8F0Ip.xml | 2 + .../aEHSZBIY-yve10yGis12Zr5DLZod.xml | 2 + .../aEHSZBIY-yve10yGis12Zr5DLZop.xml | 2 + .../j4xwF_j8iFTVayUMfxLgMnTbencd.xml | 2 + .../j4xwF_j8iFTVayUMfxLgMnTbencp.xml | 2 + .../r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml | 2 + .../r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml | 2 + .../9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml | 2 + .../9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml | 2 + .../DZm04spwjRy--my25c3HQijqtP0d.xml | 2 + .../DZm04spwjRy--my25c3HQijqtP0p.xml | 2 + .../MFn52BkouEopT6vbPU3zED0ZDsId.xml | 2 + .../MFn52BkouEopT6vbPU3zED0ZDsIp.xml | 2 + .../SXm5a-Het5-ZRrlKiXpnmegsBTod.xml | 2 + .../SXm5a-Het5-ZRrlKiXpnmegsBTop.xml | 2 + .../gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml | 2 + .../gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml | 2 + resources/project/Project.xml | 2 + .../GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml | 2 + .../GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml | 2 + .../PEXPsKGais8E1vYnZ-aE3yL8sEId.xml | 2 + .../PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml | 2 + .../qfVPavq7Uqg1pEisrDaCVbtvDJId.xml | 2 + .../qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml | 2 + .../TMZL23F8wOV5HqNkYCt0VinxHtYd.xml | 2 + .../TMZL23F8wOV5HqNkYCt0VinxHtYp.xml | 2 + .../cInDQx1HNLTcw32-hZCkckIZJTkd.xml | 2 + .../cInDQx1HNLTcw32-hZCkckIZJTkp.xml | 2 + .../o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml | 2 + .../o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml | 2 + .../A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml | 2 + .../A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml | 2 + .../J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml | 2 + .../J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml | 2 + .../M3GsPDq-ce6V_JJNmshvkjerLK8d.xml | 2 + .../M3GsPDq-ce6V_JJNmshvkjerLK8p.xml | 2 + .../ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml | 2 + .../ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml | 2 + .../Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml | 2 + .../Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml | 2 + .../v66rjJH86SBncOTEIRzsIn29ae4d.xml | 2 + .../v66rjJH86SBncOTEIRzsIn29ae4p.xml | 2 + .../lmBC8LfYX7EpuYL-T1EededA118d.xml | 2 + .../lmBC8LfYX7EpuYL-T1EededA118p.xml | 2 + .../NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml | 2 + .../NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml | 2 + .../quWI0YIIYG9pN30wY38QKH6nHv4d.xml | 2 + .../quWI0YIIYG9pN30wY38QKH6nHv4p.xml | 2 + .../3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml | 2 + .../3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml | 2 + .../VANTmkLQY6xmL2oTlIa7vq1J98wd.xml | 2 + .../VANTmkLQY6xmL2oTlIa7vq1J98wp.xml | 2 + .../FIvnojW6RLtZgaABMtVnpkJSJqId.xml | 2 + .../FIvnojW6RLtZgaABMtVnpkJSJqIp.xml | 2 + .../oiBudmzlq222MZ8BwkOnRAtvJDAd.xml | 2 + .../oiBudmzlq222MZ8BwkOnRAtvJDAp.xml | 2 + .../Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml | 2 + .../Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml | 2 + .../MDnWniV77enVhZz9_UXTaL0JBX8d.xml | 2 + .../MDnWniV77enVhZz9_UXTaL0JBX8p.xml | 2 + .../Q397CBsXzx2zKNQDxi16JP-aBsId.xml | 2 + .../Q397CBsXzx2zKNQDxi16JP-aBsIp.xml | 2 + .../QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml | 2 + .../QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml | 2 + .../QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml | 2 + .../QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml | 2 + .../TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml | 2 + .../TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml | 2 + .../XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml | 2 + .../XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml | 2 + .../_62woAnzhP2vhMa00Uh-rC4Awdcd.xml | 2 + .../_62woAnzhP2vhMa00Uh-rC4Awdcp.xml | 2 + .../aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml | 2 + .../aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml | 2 + .../bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml | 2 + .../bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml | 2 + .../qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml | 2 + .../qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml | 2 + .../zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml | 2 + .../zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml | 2 + .../Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml | 2 + .../Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml | 2 + .../QC--iM64JrOQuKvG1jexlQeIeLkd.xml | 2 + .../QC--iM64JrOQuKvG1jexlQeIeLkp.xml | 2 + .../_-JNOLDhfGqXArGMpy1suCtgWXMd.xml | 2 + .../_-JNOLDhfGqXArGMpy1suCtgWXMp.xml | 2 + .../root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml | 2 + .../root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml | 2 + .../root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml | 2 + .../root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml | 2 + .../root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml | 2 + .../root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml | 2 + .../root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml | 2 + .../root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml | 2 + .../root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml | 2 + resources/project/rootp.xml | 2 + .../mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml | 2 + .../mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml | 2 + .../-e4XloybOK7yGSjQwdC4NSCunqId.xml | 6 + .../-e4XloybOK7yGSjQwdC4NSCunqIp.xml | 2 + .../CT9Miou6DxD317AvUkG0AxeLQZ4d.xml | 6 + .../CT9Miou6DxD317AvUkG0AxeLQZ4p.xml | 2 + .../SyRNys_JvjAdM_6mzWaNDXtRV84d.xml | 2 + .../SyRNys_JvjAdM_6mzWaNDXtRV84p.xml | 2 + .../W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml | 2 + .../W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml | 2 + .../cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml | 2 + .../cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml | 2 + .../dHTYEePuadCCzyAuQBl7PQUyNNkd.xml | 6 + .../dHTYEePuadCCzyAuQBl7PQUyNNkp.xml | 2 + .../i6RsrbhqjXuacJMckiJ55cynMiYd.xml | 6 + .../i6RsrbhqjXuacJMckiJ55cynMiYp.xml | 2 + .../on5V8t1iGoKMy6XZAskaWOm3fDMd.xml | 2 + .../on5V8t1iGoKMy6XZAskaWOm3fDMp.xml | 2 + toolbox.ignore | 32 +++ 192 files changed, 1545 insertions(+), 146 deletions(-) create mode 100644 Polypheny_Connector.prj create mode 100644 app/bin/main/PolyphenyWrapperTestMQL.m create mode 100644 app/bin/main/PolyphenyWrapperTestSQL.m create mode 100644 app/src/main/java/PolyphenyWrapperTestMQL.m create mode 100644 app/src/main/java/PolyphenyWrapperTestSQL.m create mode 100644 resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml create mode 100644 resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml create mode 100644 resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml create mode 100644 resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssd.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssp.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgd.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgp.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsd.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsp.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgd.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgp.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsd.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsp.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3od.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3op.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4d.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4p.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml create mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml create mode 100644 resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml create mode 100644 resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml create mode 100644 resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml create mode 100644 resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml create mode 100644 resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVId.xml create mode 100644 resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml create mode 100644 resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEd.xml create mode 100644 resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEp.xml create mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8d.xml create mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8p.xml create mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml create mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml create mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml create mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml create mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml create mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml create mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml create mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml create mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUd.xml create mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUp.xml create mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml create mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml create mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMd.xml create mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMp.xml create mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml create mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml create mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml create mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml create mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYd.xml create mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYp.xml create mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml create mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4d.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4p.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Id.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Ip.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZod.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZop.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencd.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencp.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml create mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0d.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0p.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsId.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsIp.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTod.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTop.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml create mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml create mode 100644 resources/project/Project.xml create mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml create mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml create mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEId.xml create mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml create mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJId.xml create mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml create mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYd.xml create mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYp.xml create mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkd.xml create mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkp.xml create mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml create mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml create mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml create mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml create mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml create mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml create mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8d.xml create mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8p.xml create mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml create mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml create mode 100644 resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml create mode 100644 resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml create mode 100644 resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4d.xml create mode 100644 resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4p.xml create mode 100644 resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118d.xml create mode 100644 resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118p.xml create mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml create mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml create mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4d.xml create mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4p.xml create mode 100644 resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml create mode 100644 resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml create mode 100644 resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wd.xml create mode 100644 resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wp.xml create mode 100644 resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqId.xml create mode 100644 resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqIp.xml create mode 100644 resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAd.xml create mode 100644 resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAp.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8d.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8p.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsId.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsIp.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcd.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcp.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml create mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml create mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml create mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkd.xml create mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkp.xml create mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMd.xml create mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMp.xml create mode 100644 resources/project/root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml create mode 100644 resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml create mode 100644 resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml create mode 100644 resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml create mode 100644 resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml create mode 100644 resources/project/root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml create mode 100644 resources/project/root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml create mode 100644 resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml create mode 100644 resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml create mode 100644 resources/project/rootp.xml create mode 100644 resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml create mode 100644 resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqId.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqIp.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4d.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4p.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84d.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84p.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkd.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkp.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYd.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYp.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMd.xml create mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMp.xml create mode 100644 toolbox.ignore diff --git a/Polypheny_Connector.prj b/Polypheny_Connector.prj new file mode 100644 index 0000000..6b95f98 --- /dev/null +++ b/Polypheny_Connector.prj @@ -0,0 +1,2 @@ + + diff --git a/app/bin/main/Polypheny.m b/app/bin/main/Polypheny.m index 527636b..dfd0721 100644 --- a/app/bin/main/Polypheny.m +++ b/app/bin/main/Polypheny.m @@ -1,7 +1,7 @@ classdef Polypheny < handle % POLYPHENY MATLAB wrapper for the Polypheny Java connector. Wraps polyphenyconnector.PolyphenyConnection % and polyphenyconnector.QueryExecutor to run queries from MATLAB - properties (Access = private) + properties ( Access = private ) polyConnection % Java PolyphenyConnection queryExecutor % Java QueryExecutor @@ -11,22 +11,22 @@ function PolyWrapper = Polypheny( host, port, user, password ) % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor - % LANGUAGE: The database language ('sql', 'mongo', 'cypher') - % HOST: Database host (e.g. 'localhost') - % PORT: Database port (integer) + % LANGUAGE: The database language ( 'sql', 'mongo', 'cypher' ) + % HOST: Database host ( e.g. 'localhost' ) + % PORT: Database port ( integer ) % USER: Username % PASSWORD: Password % This makes sure that Matlab sees Java classes supplied by the .jar files in the Matlabtoolbox PolyphenyConnector.mtlbx try - if ~polypheny.Polypheny.hasPolypheny() - startup(); + if ~polypheny.Polypheny.hasPolypheny( ) + startup( ); end - PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); - PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + PolyWrapper.polyConnection = javaObject( "polyphenyconnector.PolyphenyConnection",host, int32( port ), user, password ); + PolyWrapper.queryExecutor = javaObject( "polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); catch ME %Matlab Exception - disp("Error: " + ME.message) + disp( "Error: " + ME.message ) end end @@ -41,85 +41,96 @@ % @return matlab_result: The result of the query -> return type differs for SQL,Mongo and Cypher - try - java_result = PolyWrapper.queryExecutor.execute(string(language), string(namespace), queryStr ); - - % SQL case - if language == "sql" - if isempty( java_result ) - matlab_result = []; - - elseif isscalar( java_result ) - matlab_result = java_result; - - elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 - matlab_result = cell(java_result); - colNames = cell(matlab_result{1}); - data = cell(matlab_result{2}); % Object[][] → MATLAB cell - matlab_result = cell2table(data, 'VariableNames', colNames); - - else - matlab_result = []; % fallback to avoid cell2table crash - end - - % mongo case - elseif language == "mongo" - - if isscalar( java_result) - matlab_result = java_result; - - elseif ischar( java_result ) || isstring (java_result) - matlab_result = string(java_result); + try + java_result = PolyWrapper.queryExecutor.execute( string( language ), string( namespace ), queryStr ); + + switch lower( language ) + case "sql" + if isempty( java_result ) + matlab_result = []; + elseif isscalar( java_result ) + matlab_result = java_result; + elseif isa( java_result,'java.lang.Object[]' ) && numel( java_result )==2 + tmp = cell( java_result ); + colNames = cell( tmp{1} ); + data = cell( tmp{2} ); + matlab_result = cell2table( data, 'VariableNames', colNames ); + else + matlab_result = []; + end - else - try - matlab_result = java_result - catch ME %Matlab Exception - disp("Error: " + ME.message) + case "mongo" + if isa( java_result, 'java.util.List' ) + % Current driver behavior: always returns List of JSON docs + matlab_result = string(java_result); + elseif isnumeric( java_result ) + % Not observed in current driver, but kept for forward compatibility + % (e.g. if Polypheny ever returns scalar counts directly) + matlab_result = java_result; + else + error( "Unexpected Mongo result type: %s", class( java_result ) ); end - - end - % mongo case - elseif language == "cypher" + case "cypher" + % TODO: integrate once Cypher executor is ready + error( "Cypher not supported yet." ); + + otherwise + error( "Unsupported language: %s", language ); end - - catch ME %Matlab Exception - disp("Error: " + ME.message) - end + catch ME + error( "Query execution failed: %s", ME.message ); + end end function matlab_result = queryBatch( PolyWrapper, language, namespace, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements - % QUERYLIST: A cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) + % QUERYLIST: A cell array of SQL strings ( INSERT, UPDATE, DELETE, etc. ) % % Returns: int array with rows affected per statement - if ~iscell( queryList ) % cell is the Matlab List type - error( 'queryBatch expects a cell array of SQL strings' ); + if ~iscell( queryList ) + error( 'queryBatch expects a cell array of query strings' ); end - + javaList = java.util.ArrayList(); - for i = 1:numel(queryList) - javaList.add( string(queryList{i}) ); + for i = 1:numel( queryList ) + javaList.add( string(queryList{i} ) ); end - java_result = PolyWrapper.queryExecutor.executeBatch( string(language), string(namespace), javaList ); - matlab_result = double(java_result(:))'; + + switch lower(language) + case "sql" + java_result = PolyWrapper.queryExecutor.executeBatchSql( javaList ); + %matlab_result = double(java_result(:))'; + vals = double(java_result(:)); % convert Java int[] to MATLAB column vector + matlab_result = array2table(vals, 'VariableNames', {'RowsAffected'}); + + case "mongo" + java_result = PolyWrapper.queryExecutor.executeBatchMongo( string(namespace), javaList ); + matlab_result = string( java_result ); % outer list + + case "cypher" + error( "Batch execution for Cypher not yet implemented." ); + + otherwise + error( "Unsupported language: %s", language ); + end + end function close( PolyWrapper ) % close( POLYWRAPPER ): Close the Java connection % POLYWRAPPER: The PolyWrapper Matlab object - PolyWrapper.polyConnection.close(); + PolyWrapper.polyConnection.close( ); end end - methods (Static) - function flag = hasPolypheny() - % HASPOLYPHENY Returns true if Polypheny Java classes are available because the exist('polyphenyconnector.PolyphenyConnection','class') + methods ( Static ) + function flag = hasPolypheny( ) + % HASPOLYPHENY Returns true if Polypheny Java classes are available because the exist( 'polyphenyconnector.PolyphenyConnection','class' ) % returns 8 if Matlab sees the Java class and 0 otherwise. - flag = ( exist('polyphenyconnector.PolyphenyConnection','class') == 8 ); + flag = ( exist( 'polyphenyconnector.PolyphenyConnection','class' ) == 8 ); end end diff --git a/app/bin/main/PolyphenyWrapperTestMQL.m b/app/bin/main/PolyphenyWrapperTestMQL.m new file mode 100644 index 0000000..e992fc1 --- /dev/null +++ b/app/bin/main/PolyphenyWrapperTestMQL.m @@ -0,0 +1,245 @@ +classdef PolyphenyWrapperTestMQL < matlab.unittest.TestCase + properties + conn + end + + methods(TestClassSetup) + function setUpNamespaceAndCollection(testCase) + clc; + % open connection once + testCase.conn = polypheny.Polypheny("localhost",20590,"pa",""); + + % try create collection + try + testCase.conn.query("mongo","mongotest", ... + 'db.createCollection("unittest_collection")'); + catch + end + end + end + + methods(TestClassTeardown) + function tearDownNamespaceAndCollection(testCase) + try + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.drop()'); + catch + end + testCase.conn.close(); + end + end + + methods(TestMethodSetup) + function clearCollection(testCase) + try + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.deleteMany({})'); + catch + end + end + end + + methods(TestMethodTeardown) + function clearCollectionAfter(testCase) + try + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.deleteMany({})'); + catch + end + end + end + + methods(Test) + + function testDeleteManyRemovesAllDocs(testCase) + % Drop & recreate collection + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.drop()'); + testCase.conn.query("mongo","mongotest", 'db.createCollection("unittest_collection")'); + + % Insert three documents + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.insertOne({"id":1,"name":"Alice"})'); + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.insertOne({"id":2,"name":"Bob"})'); + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.insertOne({"id":3,"name":"Ciri"})'); + + % Call deleteMany({}) + ack = testCase.conn.query("mongo","mongotest", 'db.unittest_collection.deleteMany({})'); + disp("Ack from deleteMany:"); + disp(ack); + + % Verify collection is empty + docs = testCase.conn.query("mongo","mongotest", 'db.unittest_collection.find({})'); + docs = jsondecode(docs); + testCase.verifyEmpty(docs, "Collection should be empty after deleteMany({})"); + end + + function testInsertManyAndNestedDocument(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":14})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":20})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":24})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":30,"adress":{"Country":"Switzerland","Code":4051}})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({"age":{$gt:29}})'); + disp(docs) + decoded = jsondecode(docs); + disp(decoded) + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"age":30')); + end + + function testBooleanField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"flag":true})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + decoded = jsondecode(docs); + disp(decoded) + testCase.verifyTrue(contains(docs(1),'"flag":true')); + testCase.verifyClass(decoded.flag, 'logical'); % asserts that class(decoded.flag) == logical + end + + function testIntegerAgeField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":42})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"age":42')); + end + + function testStringField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"name":"Alice"})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"name":"Alice"')); + end + + function testLongField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"big":1111111111111111111})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"big":1111111111111111111')); + end + + function testDoubleField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"pi":3.14159})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"pi":3.14159')); + end + + function testInsertAndQueryTwoDocsRawJson(testCase) + % Clean collection + testCase.conn.query("mongo","mongotest",'db.unittest_collection.drop()'); + testCase.conn.query("mongo","mongotest",'db.createCollection("unittest_collection")'); + + % Insert two docs + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.insertOne({"id":1,"name":"Alice"})'); + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.insertOne({"id":2,"name":"Bob"})'); + + % Query back + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp("Raw JSON:"); + disp(docs); + decoded = jsondecode(docs); + disp(decoded) + + % Assert raw JSON is exactly what we want + testCase.verifyTrue(contains(docs(1),'"name":"Alice"')); + testCase.verifyTrue(contains(docs(1),'"name":"Bob"')); + end + + + function testCountDocuments(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"name":"Bob"})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.countDocuments({})'); + disp(docs) + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'{"count":1}')); + end + + function testArrayField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"scores":[1,2,3]})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs,'"scores":[1,2,3]')); + end + + function testFindOnEmptyCollection(testCase) + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyEqual(docs,"[]"); + end + + function testInsertManyAndFindMultiple(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"id":10,"name":"A"})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"id":11,"name":"B"})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyTrue(contains(docs,'"id":10')) + testCase.verifyTrue(contains(docs,'"name":"A"')) + testCase.verifyTrue(contains(docs,'"id":11')) + testCase.verifyTrue(contains(docs,'"name":"B"')) + end + + function testBatchInsertAndFind(testCase) + queries = { ... + 'db.unittest_collection.insertOne({"name":"Alice","age":25})', ... + 'db.unittest_collection.insertOne({"name":"Alice","age":20})', ... + 'db.unittest_collection.insertOne({"name":"Bob","age":30})' }; + ignore = testCase.conn.queryBatch("mongo","mongotest",queries); + queries2 = { ... + 'db.unittest_collection.find({"name":"Alice"})', ... + 'db.unittest_collection.find({"name":"Alice","age":20})', ... + 'db.unittest_collection.find({"name":"Bob","age":30})' }; + docs = testCase.conn.queryBatch("mongo","mongotest", queries2); + disp(docs) + decoded = jsondecode(docs); + disp(decoded) + testCase.verifyEqual(numel(decoded{1}), 2); % 2 docs in first query + + % check names + names = {decoded{1}.name}; % cell of names + disp(names) + testCase.verifyEqual(string(names), ["Alice","Alice"]); + + end + + + function testBatchMixedOps(testCase) + queries = { ... + 'db.unittest_collection.insertOne({"name":"Charlie","active":true})', ... + 'db.unittest_collection.countDocuments({})' }; + docs = testCase.conn.queryBatch("mongo","mongotest",queries); + testCase.verifyEqual(numel(docs),1); + decoded = jsondecode(docs) + varname = fieldnames(decoded{2}) + disp(decoded{2}.count) + testCase.verifyTrue(decoded{2}.count==1); + end + + function testSyntaxErrorThrows(testCase) + badQuery = 'db.unittest_collection.insertOne({"foo":123)'; % invalid JSON + testCase.verifyError(@() testCase.conn.query("mongo","mongotest",badQuery),?MException); + end + + function testMultiStatementFails(testCase) + badMulti = [ ... + 'db.people.insertOne({"name":"Alice","age":20}); ' ... + 'db.people.insertOne({"name":"Bob","age":24}); ' ... + 'db.people.find({})' ]; + testCase.verifyError(@() testCase.conn.query("mongo","mongotest",badMulti),?MException); + end + + function testBatchRollback(testCase) + queries = { ... + 'db.unittest_collection.insertOne({"id":1,"name":"Alice"})', ... + 'db.unittest_collection.insertOne({"id":2,"name":"Bob"})', ... + 'db.unittest_collection.insertOne({"id":3,"name":"Janice"' }; % broken + testCase.verifyError(@() testCase.conn.queryBatch("mongo","mongotest",queries),?MException); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyEqual(numel(docs),1); + testCase.verifyEqual(docs,"[]") + end + + end +end diff --git a/app/bin/main/PolyphenyWrapperTestSQL.m b/app/bin/main/PolyphenyWrapperTestSQL.m new file mode 100644 index 0000000..0e11906 --- /dev/null +++ b/app/bin/main/PolyphenyWrapperTestSQL.m @@ -0,0 +1,219 @@ +classdef PolyphenyWrapperTestSQL < matlab.unittest.TestCase + properties + conn % polypheny.Polypheny wrapper + end + + methods(TestClassSetup) + function setUpNamespaceAndTable(testCase) + % Open connection once for all tests + testCase.conn = polypheny.Polypheny("localhost",20590,"pa",""); + + % Drop leftovers if they exist + try + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DROP NAMESPACE IF EXISTS unittest_namespace"); + catch + end + + % Create namespace + table for execute() + testCase.conn.query("sql","", ... + "CREATE NAMESPACE unittest_namespace"); + testCase.conn.query("sql","unittest_namespace", ... + "CREATE TABLE unittest_namespace.unittest_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY(id))"); + + % Drop and recreate batch_table + try + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.batch_table"); + catch + end + testCase.conn.query("sql","unittest_namespace", ... + "CREATE TABLE unittest_namespace.batch_table (" + ... + "emp_id INT NOT NULL, " + ... + "name VARCHAR(100), " + ... + "gender VARCHAR(10), " + ... + "birthday DATE, " + ... + "employee_id INT, " + ... + "PRIMARY KEY(emp_id))"); + end + end + + methods(TestClassTeardown) + function tearDownNamespaceAndTable(testCase) + % Cleanup after all tests + try + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.batch_table"); + testCase.conn.query("sql","", ... + "DROP NAMESPACE IF EXISTS unittest_namespace"); + catch + end + testCase.conn.close(); + end + end + + methods(TestMethodSetup) + function clearTables(testCase) + % Clear before each test + try + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.batch_table"); + catch + end + end + end + + methods(TestMethodTeardown) + function clearTablesAfter(testCase) + % Clear again after each test + try + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.batch_table"); + catch + end + end + end + + methods(Test) + function testScalarLiteral(testCase) + r = testCase.conn.query("sql","","SELECT 42 AS answer"); + testCase.verifyEqual(r,42); + end + + function testEmptyLiteral(testCase) + r = testCase.conn.query("sql","","SELECT * FROM (SELECT 1) t WHERE 1=0"); + testCase.verifyEmpty(r); + end + + function testTableLiteral(testCase) + r = testCase.conn.query("sql","unittest_namespace","SELECT 1 AS a, 2 AS b UNION ALL SELECT 3,4"); + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(r.Properties.VariableNames,{'a','b'}); + testCase.verifyEqual(height(r),2); + testCase.verifyEqual(table2cell(r(1,:)),{1,2}); + testCase.verifyEqual(table2cell(r(2,:)),{3,4}); + end + + function testInsert(testCase) + r = testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + testCase.verifyEqual(r,1); + end + + function testInsertAndSelect(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + r = testCase.conn.query("sql","unittest_namespace","SELECT id,name FROM unittest_namespace.unittest_table"); + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(r.Properties.VariableNames,{'id','name'}); + testCase.verifyEqual(height(r),1); + testCase.verifyEqual(table2cell(r),{1,'Alice'}); + end + + function testScalarFromTable(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (2,'Carol')"); + r = testCase.conn.query("sql","unittest_namespace","SELECT id FROM unittest_namespace.unittest_table WHERE name='Carol'"); + testCase.verifyEqual(r,2); + end + + function testInsertAndSelectMultipleRows(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (2,'Bob')"); + r = testCase.conn.query("sql","unittest_namespace","SELECT id,name FROM unittest_namespace.unittest_table ORDER BY id"); + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(height(r),2); + testCase.verifyEqual(table2cell(r(1,:)),{1,'Alice'}); + testCase.verifyEqual(table2cell(r(2,:)),{2,'Bob'}); + end + + function testDeleteFromTable(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (2,'Bob')"); + testCase.conn.query("sql","unittest_namespace","DELETE FROM unittest_namespace.unittest_table"); + r = testCase.conn.query("sql","unittest_namespace","SELECT * FROM unittest_namespace.unittest_table"); + testCase.verifyEmpty(r); + end + + function testBatchInsertEmployees(testCase) + queries = { + "INSERT INTO unittest_namespace.batch_table VALUES (1,'Alice','F',DATE '1990-01-15',1001)" + "INSERT INTO unittest_namespace.batch_table VALUES (2,'Bob','M',DATE '1989-05-12',1002)" + "INSERT INTO unittest_namespace.batch_table VALUES (3,'Jane','F',DATE '1992-07-23',1003)" + "INSERT INTO unittest_namespace.batch_table VALUES (4,'Tim','M',DATE '1991-03-03',1004)" + "INSERT INTO unittest_namespace.batch_table VALUES (5,'Alex','M',DATE '1994-11-11',1005)" + "INSERT INTO unittest_namespace.batch_table VALUES (6,'Mason','M',DATE '1988-04-22',1006)" + "INSERT INTO unittest_namespace.batch_table VALUES (7,'Rena','F',DATE '1995-06-17',1007)" + "INSERT INTO unittest_namespace.batch_table VALUES (8,'Christopher','M',DATE '1987-08-09',1008)" + "INSERT INTO unittest_namespace.batch_table VALUES (9,'Lexi','F',DATE '1996-09-30',1009)" + "INSERT INTO unittest_namespace.batch_table VALUES (10,'Baen','M',DATE '1990-10-05',1010)" + "INSERT INTO unittest_namespace.batch_table VALUES (11,'Ricardo','M',DATE '1986-12-12',1011)" + "INSERT INTO unittest_namespace.batch_table VALUES (12,'Tim','M',DATE '1993-02-02',1012)" + "INSERT INTO unittest_namespace.batch_table VALUES (13,'Beya','F',DATE '1994-05-25',1013)" + }; + counts = testCase.conn.queryBatch("sql","unittest_namespace",queries); + testCase.verifyEqual(height(counts),13); + disp(counts) + testCase.verifyTrue(all(counts.RowsAffected == 1)); + r = testCase.conn.query("sql","unittest_namespace","SELECT COUNT(*) FROM unittest_namespace.batch_table"); + testCase.verifyEqual(r,13); + end + + function testBatchRollbackOnFailure(testCase) + queries = { + "INSERT INTO unittest_namespace.batch_table VALUES (1,'Alice','F',DATE '1990-01-15',1001)" + "BROKEN QUERY" + }; + testCase.verifyError(@() testCase.conn.queryBatch("sql","unittest_namespace",queries),?MException); + r = testCase.conn.query("sql","unittest_namespace","SELECT * FROM unittest_namespace.batch_table"); + testCase.verifyEmpty(r); + end + + function testSyntaxError(testCase) + testCase.verifyError(@() testCase.conn.query("sql","unittest_namespace","SELEC WRONG FROM nowhere"),?MException); + end + + + function testQueryWithSpaces(testCase) + % Insert with leading spaces + testCase.conn.query("sql","unittest_namespace", ... + " INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + testCase.conn.query("sql","unittest_namespace", ... + " INSERT INTO unittest_namespace.unittest_table VALUES (2,'Bob')"); + + r = testCase.conn.query("sql","unittest_namespace", ... + "SELECT id,name FROM unittest_namespace.unittest_table ORDER BY id"); + + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(r.Properties.VariableNames,{'id','name'}); + testCase.verifyEqual(height(r),2); + testCase.verifyEqual(table2cell(r(1,:)),{1,'Alice'}); + testCase.verifyEqual(table2cell(r(2,:)),{2,'Bob'}); + end + + function testConnectionFailure(testCase) + testCase.verifyError(@() ... + polypheny.Polypheny("localhost",9999,"pa","").query("sql","unittest_namespace","SELECT 1"), ... + ?MException); + end + + function testCommitFailureRollback(testCase) + queries = { + "INSERT INTO unittest_namespace.batch_table VALUES (1,'Alice','F',DATE '1990-01-15',1001)" + "Intentional nonsense to produce a failure" + }; + testCase.verifyError(@() ... + testCase.conn.queryBatch("sql","unittest_namespace",queries),?MException); + + r = testCase.conn.query("sql","unittest_namespace", ... + "SELECT * FROM unittest_namespace.batch_table"); + testCase.verifyEmpty(r); + end + + end + +end \ No newline at end of file diff --git a/app/src/main/java/Polypheny.m b/app/src/main/java/Polypheny.m index 527636b..dfd0721 100644 --- a/app/src/main/java/Polypheny.m +++ b/app/src/main/java/Polypheny.m @@ -1,7 +1,7 @@ classdef Polypheny < handle % POLYPHENY MATLAB wrapper for the Polypheny Java connector. Wraps polyphenyconnector.PolyphenyConnection % and polyphenyconnector.QueryExecutor to run queries from MATLAB - properties (Access = private) + properties ( Access = private ) polyConnection % Java PolyphenyConnection queryExecutor % Java QueryExecutor @@ -11,22 +11,22 @@ function PolyWrapper = Polypheny( host, port, user, password ) % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor - % LANGUAGE: The database language ('sql', 'mongo', 'cypher') - % HOST: Database host (e.g. 'localhost') - % PORT: Database port (integer) + % LANGUAGE: The database language ( 'sql', 'mongo', 'cypher' ) + % HOST: Database host ( e.g. 'localhost' ) + % PORT: Database port ( integer ) % USER: Username % PASSWORD: Password % This makes sure that Matlab sees Java classes supplied by the .jar files in the Matlabtoolbox PolyphenyConnector.mtlbx try - if ~polypheny.Polypheny.hasPolypheny() - startup(); + if ~polypheny.Polypheny.hasPolypheny( ) + startup( ); end - PolyWrapper.polyConnection = javaObject("polyphenyconnector.PolyphenyConnection",host, int32(port), user, password ); - PolyWrapper.queryExecutor = javaObject("polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + PolyWrapper.polyConnection = javaObject( "polyphenyconnector.PolyphenyConnection",host, int32( port ), user, password ); + PolyWrapper.queryExecutor = javaObject( "polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); catch ME %Matlab Exception - disp("Error: " + ME.message) + disp( "Error: " + ME.message ) end end @@ -41,85 +41,96 @@ % @return matlab_result: The result of the query -> return type differs for SQL,Mongo and Cypher - try - java_result = PolyWrapper.queryExecutor.execute(string(language), string(namespace), queryStr ); - - % SQL case - if language == "sql" - if isempty( java_result ) - matlab_result = []; - - elseif isscalar( java_result ) - matlab_result = java_result; - - elseif isa(java_result,'java.lang.Object[]') && numel(java_result) == 2 - matlab_result = cell(java_result); - colNames = cell(matlab_result{1}); - data = cell(matlab_result{2}); % Object[][] → MATLAB cell - matlab_result = cell2table(data, 'VariableNames', colNames); - - else - matlab_result = []; % fallback to avoid cell2table crash - end - - % mongo case - elseif language == "mongo" - - if isscalar( java_result) - matlab_result = java_result; - - elseif ischar( java_result ) || isstring (java_result) - matlab_result = string(java_result); + try + java_result = PolyWrapper.queryExecutor.execute( string( language ), string( namespace ), queryStr ); + + switch lower( language ) + case "sql" + if isempty( java_result ) + matlab_result = []; + elseif isscalar( java_result ) + matlab_result = java_result; + elseif isa( java_result,'java.lang.Object[]' ) && numel( java_result )==2 + tmp = cell( java_result ); + colNames = cell( tmp{1} ); + data = cell( tmp{2} ); + matlab_result = cell2table( data, 'VariableNames', colNames ); + else + matlab_result = []; + end - else - try - matlab_result = java_result - catch ME %Matlab Exception - disp("Error: " + ME.message) + case "mongo" + if isa( java_result, 'java.util.List' ) + % Current driver behavior: always returns List of JSON docs + matlab_result = string(java_result); + elseif isnumeric( java_result ) + % Not observed in current driver, but kept for forward compatibility + % (e.g. if Polypheny ever returns scalar counts directly) + matlab_result = java_result; + else + error( "Unexpected Mongo result type: %s", class( java_result ) ); end - - end - % mongo case - elseif language == "cypher" + case "cypher" + % TODO: integrate once Cypher executor is ready + error( "Cypher not supported yet." ); + + otherwise + error( "Unsupported language: %s", language ); end - - catch ME %Matlab Exception - disp("Error: " + ME.message) - end + catch ME + error( "Query execution failed: %s", ME.message ); + end end function matlab_result = queryBatch( PolyWrapper, language, namespace, queryList ) % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements - % QUERYLIST: A cell array of SQL strings (INSERT, UPDATE, DELETE, etc.) + % QUERYLIST: A cell array of SQL strings ( INSERT, UPDATE, DELETE, etc. ) % % Returns: int array with rows affected per statement - if ~iscell( queryList ) % cell is the Matlab List type - error( 'queryBatch expects a cell array of SQL strings' ); + if ~iscell( queryList ) + error( 'queryBatch expects a cell array of query strings' ); end - + javaList = java.util.ArrayList(); - for i = 1:numel(queryList) - javaList.add( string(queryList{i}) ); + for i = 1:numel( queryList ) + javaList.add( string(queryList{i} ) ); end - java_result = PolyWrapper.queryExecutor.executeBatch( string(language), string(namespace), javaList ); - matlab_result = double(java_result(:))'; + + switch lower(language) + case "sql" + java_result = PolyWrapper.queryExecutor.executeBatchSql( javaList ); + %matlab_result = double(java_result(:))'; + vals = double(java_result(:)); % convert Java int[] to MATLAB column vector + matlab_result = array2table(vals, 'VariableNames', {'RowsAffected'}); + + case "mongo" + java_result = PolyWrapper.queryExecutor.executeBatchMongo( string(namespace), javaList ); + matlab_result = string( java_result ); % outer list + + case "cypher" + error( "Batch execution for Cypher not yet implemented." ); + + otherwise + error( "Unsupported language: %s", language ); + end + end function close( PolyWrapper ) % close( POLYWRAPPER ): Close the Java connection % POLYWRAPPER: The PolyWrapper Matlab object - PolyWrapper.polyConnection.close(); + PolyWrapper.polyConnection.close( ); end end - methods (Static) - function flag = hasPolypheny() - % HASPOLYPHENY Returns true if Polypheny Java classes are available because the exist('polyphenyconnector.PolyphenyConnection','class') + methods ( Static ) + function flag = hasPolypheny( ) + % HASPOLYPHENY Returns true if Polypheny Java classes are available because the exist( 'polyphenyconnector.PolyphenyConnection','class' ) % returns 8 if Matlab sees the Java class and 0 otherwise. - flag = ( exist('polyphenyconnector.PolyphenyConnection','class') == 8 ); + flag = ( exist( 'polyphenyconnector.PolyphenyConnection','class' ) == 8 ); end end diff --git a/app/src/main/java/PolyphenyWrapperTestMQL.m b/app/src/main/java/PolyphenyWrapperTestMQL.m new file mode 100644 index 0000000..e992fc1 --- /dev/null +++ b/app/src/main/java/PolyphenyWrapperTestMQL.m @@ -0,0 +1,245 @@ +classdef PolyphenyWrapperTestMQL < matlab.unittest.TestCase + properties + conn + end + + methods(TestClassSetup) + function setUpNamespaceAndCollection(testCase) + clc; + % open connection once + testCase.conn = polypheny.Polypheny("localhost",20590,"pa",""); + + % try create collection + try + testCase.conn.query("mongo","mongotest", ... + 'db.createCollection("unittest_collection")'); + catch + end + end + end + + methods(TestClassTeardown) + function tearDownNamespaceAndCollection(testCase) + try + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.drop()'); + catch + end + testCase.conn.close(); + end + end + + methods(TestMethodSetup) + function clearCollection(testCase) + try + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.deleteMany({})'); + catch + end + end + end + + methods(TestMethodTeardown) + function clearCollectionAfter(testCase) + try + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.deleteMany({})'); + catch + end + end + end + + methods(Test) + + function testDeleteManyRemovesAllDocs(testCase) + % Drop & recreate collection + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.drop()'); + testCase.conn.query("mongo","mongotest", 'db.createCollection("unittest_collection")'); + + % Insert three documents + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.insertOne({"id":1,"name":"Alice"})'); + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.insertOne({"id":2,"name":"Bob"})'); + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.insertOne({"id":3,"name":"Ciri"})'); + + % Call deleteMany({}) + ack = testCase.conn.query("mongo","mongotest", 'db.unittest_collection.deleteMany({})'); + disp("Ack from deleteMany:"); + disp(ack); + + % Verify collection is empty + docs = testCase.conn.query("mongo","mongotest", 'db.unittest_collection.find({})'); + docs = jsondecode(docs); + testCase.verifyEmpty(docs, "Collection should be empty after deleteMany({})"); + end + + function testInsertManyAndNestedDocument(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":14})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":20})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":24})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":30,"adress":{"Country":"Switzerland","Code":4051}})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({"age":{$gt:29}})'); + disp(docs) + decoded = jsondecode(docs); + disp(decoded) + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"age":30')); + end + + function testBooleanField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"flag":true})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + decoded = jsondecode(docs); + disp(decoded) + testCase.verifyTrue(contains(docs(1),'"flag":true')); + testCase.verifyClass(decoded.flag, 'logical'); % asserts that class(decoded.flag) == logical + end + + function testIntegerAgeField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":42})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"age":42')); + end + + function testStringField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"name":"Alice"})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"name":"Alice"')); + end + + function testLongField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"big":1111111111111111111})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"big":1111111111111111111')); + end + + function testDoubleField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"pi":3.14159})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"pi":3.14159')); + end + + function testInsertAndQueryTwoDocsRawJson(testCase) + % Clean collection + testCase.conn.query("mongo","mongotest",'db.unittest_collection.drop()'); + testCase.conn.query("mongo","mongotest",'db.createCollection("unittest_collection")'); + + % Insert two docs + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.insertOne({"id":1,"name":"Alice"})'); + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.insertOne({"id":2,"name":"Bob"})'); + + % Query back + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp("Raw JSON:"); + disp(docs); + decoded = jsondecode(docs); + disp(decoded) + + % Assert raw JSON is exactly what we want + testCase.verifyTrue(contains(docs(1),'"name":"Alice"')); + testCase.verifyTrue(contains(docs(1),'"name":"Bob"')); + end + + + function testCountDocuments(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"name":"Bob"})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.countDocuments({})'); + disp(docs) + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'{"count":1}')); + end + + function testArrayField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"scores":[1,2,3]})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs,'"scores":[1,2,3]')); + end + + function testFindOnEmptyCollection(testCase) + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyEqual(docs,"[]"); + end + + function testInsertManyAndFindMultiple(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"id":10,"name":"A"})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"id":11,"name":"B"})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyTrue(contains(docs,'"id":10')) + testCase.verifyTrue(contains(docs,'"name":"A"')) + testCase.verifyTrue(contains(docs,'"id":11')) + testCase.verifyTrue(contains(docs,'"name":"B"')) + end + + function testBatchInsertAndFind(testCase) + queries = { ... + 'db.unittest_collection.insertOne({"name":"Alice","age":25})', ... + 'db.unittest_collection.insertOne({"name":"Alice","age":20})', ... + 'db.unittest_collection.insertOne({"name":"Bob","age":30})' }; + ignore = testCase.conn.queryBatch("mongo","mongotest",queries); + queries2 = { ... + 'db.unittest_collection.find({"name":"Alice"})', ... + 'db.unittest_collection.find({"name":"Alice","age":20})', ... + 'db.unittest_collection.find({"name":"Bob","age":30})' }; + docs = testCase.conn.queryBatch("mongo","mongotest", queries2); + disp(docs) + decoded = jsondecode(docs); + disp(decoded) + testCase.verifyEqual(numel(decoded{1}), 2); % 2 docs in first query + + % check names + names = {decoded{1}.name}; % cell of names + disp(names) + testCase.verifyEqual(string(names), ["Alice","Alice"]); + + end + + + function testBatchMixedOps(testCase) + queries = { ... + 'db.unittest_collection.insertOne({"name":"Charlie","active":true})', ... + 'db.unittest_collection.countDocuments({})' }; + docs = testCase.conn.queryBatch("mongo","mongotest",queries); + testCase.verifyEqual(numel(docs),1); + decoded = jsondecode(docs) + varname = fieldnames(decoded{2}) + disp(decoded{2}.count) + testCase.verifyTrue(decoded{2}.count==1); + end + + function testSyntaxErrorThrows(testCase) + badQuery = 'db.unittest_collection.insertOne({"foo":123)'; % invalid JSON + testCase.verifyError(@() testCase.conn.query("mongo","mongotest",badQuery),?MException); + end + + function testMultiStatementFails(testCase) + badMulti = [ ... + 'db.people.insertOne({"name":"Alice","age":20}); ' ... + 'db.people.insertOne({"name":"Bob","age":24}); ' ... + 'db.people.find({})' ]; + testCase.verifyError(@() testCase.conn.query("mongo","mongotest",badMulti),?MException); + end + + function testBatchRollback(testCase) + queries = { ... + 'db.unittest_collection.insertOne({"id":1,"name":"Alice"})', ... + 'db.unittest_collection.insertOne({"id":2,"name":"Bob"})', ... + 'db.unittest_collection.insertOne({"id":3,"name":"Janice"' }; % broken + testCase.verifyError(@() testCase.conn.queryBatch("mongo","mongotest",queries),?MException); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyEqual(numel(docs),1); + testCase.verifyEqual(docs,"[]") + end + + end +end diff --git a/app/src/main/java/PolyphenyWrapperTestSQL.m b/app/src/main/java/PolyphenyWrapperTestSQL.m new file mode 100644 index 0000000..0e11906 --- /dev/null +++ b/app/src/main/java/PolyphenyWrapperTestSQL.m @@ -0,0 +1,219 @@ +classdef PolyphenyWrapperTestSQL < matlab.unittest.TestCase + properties + conn % polypheny.Polypheny wrapper + end + + methods(TestClassSetup) + function setUpNamespaceAndTable(testCase) + % Open connection once for all tests + testCase.conn = polypheny.Polypheny("localhost",20590,"pa",""); + + % Drop leftovers if they exist + try + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DROP NAMESPACE IF EXISTS unittest_namespace"); + catch + end + + % Create namespace + table for execute() + testCase.conn.query("sql","", ... + "CREATE NAMESPACE unittest_namespace"); + testCase.conn.query("sql","unittest_namespace", ... + "CREATE TABLE unittest_namespace.unittest_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY(id))"); + + % Drop and recreate batch_table + try + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.batch_table"); + catch + end + testCase.conn.query("sql","unittest_namespace", ... + "CREATE TABLE unittest_namespace.batch_table (" + ... + "emp_id INT NOT NULL, " + ... + "name VARCHAR(100), " + ... + "gender VARCHAR(10), " + ... + "birthday DATE, " + ... + "employee_id INT, " + ... + "PRIMARY KEY(emp_id))"); + end + end + + methods(TestClassTeardown) + function tearDownNamespaceAndTable(testCase) + % Cleanup after all tests + try + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.batch_table"); + testCase.conn.query("sql","", ... + "DROP NAMESPACE IF EXISTS unittest_namespace"); + catch + end + testCase.conn.close(); + end + end + + methods(TestMethodSetup) + function clearTables(testCase) + % Clear before each test + try + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.batch_table"); + catch + end + end + end + + methods(TestMethodTeardown) + function clearTablesAfter(testCase) + % Clear again after each test + try + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.batch_table"); + catch + end + end + end + + methods(Test) + function testScalarLiteral(testCase) + r = testCase.conn.query("sql","","SELECT 42 AS answer"); + testCase.verifyEqual(r,42); + end + + function testEmptyLiteral(testCase) + r = testCase.conn.query("sql","","SELECT * FROM (SELECT 1) t WHERE 1=0"); + testCase.verifyEmpty(r); + end + + function testTableLiteral(testCase) + r = testCase.conn.query("sql","unittest_namespace","SELECT 1 AS a, 2 AS b UNION ALL SELECT 3,4"); + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(r.Properties.VariableNames,{'a','b'}); + testCase.verifyEqual(height(r),2); + testCase.verifyEqual(table2cell(r(1,:)),{1,2}); + testCase.verifyEqual(table2cell(r(2,:)),{3,4}); + end + + function testInsert(testCase) + r = testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + testCase.verifyEqual(r,1); + end + + function testInsertAndSelect(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + r = testCase.conn.query("sql","unittest_namespace","SELECT id,name FROM unittest_namespace.unittest_table"); + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(r.Properties.VariableNames,{'id','name'}); + testCase.verifyEqual(height(r),1); + testCase.verifyEqual(table2cell(r),{1,'Alice'}); + end + + function testScalarFromTable(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (2,'Carol')"); + r = testCase.conn.query("sql","unittest_namespace","SELECT id FROM unittest_namespace.unittest_table WHERE name='Carol'"); + testCase.verifyEqual(r,2); + end + + function testInsertAndSelectMultipleRows(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (2,'Bob')"); + r = testCase.conn.query("sql","unittest_namespace","SELECT id,name FROM unittest_namespace.unittest_table ORDER BY id"); + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(height(r),2); + testCase.verifyEqual(table2cell(r(1,:)),{1,'Alice'}); + testCase.verifyEqual(table2cell(r(2,:)),{2,'Bob'}); + end + + function testDeleteFromTable(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (2,'Bob')"); + testCase.conn.query("sql","unittest_namespace","DELETE FROM unittest_namespace.unittest_table"); + r = testCase.conn.query("sql","unittest_namespace","SELECT * FROM unittest_namespace.unittest_table"); + testCase.verifyEmpty(r); + end + + function testBatchInsertEmployees(testCase) + queries = { + "INSERT INTO unittest_namespace.batch_table VALUES (1,'Alice','F',DATE '1990-01-15',1001)" + "INSERT INTO unittest_namespace.batch_table VALUES (2,'Bob','M',DATE '1989-05-12',1002)" + "INSERT INTO unittest_namespace.batch_table VALUES (3,'Jane','F',DATE '1992-07-23',1003)" + "INSERT INTO unittest_namespace.batch_table VALUES (4,'Tim','M',DATE '1991-03-03',1004)" + "INSERT INTO unittest_namespace.batch_table VALUES (5,'Alex','M',DATE '1994-11-11',1005)" + "INSERT INTO unittest_namespace.batch_table VALUES (6,'Mason','M',DATE '1988-04-22',1006)" + "INSERT INTO unittest_namespace.batch_table VALUES (7,'Rena','F',DATE '1995-06-17',1007)" + "INSERT INTO unittest_namespace.batch_table VALUES (8,'Christopher','M',DATE '1987-08-09',1008)" + "INSERT INTO unittest_namespace.batch_table VALUES (9,'Lexi','F',DATE '1996-09-30',1009)" + "INSERT INTO unittest_namespace.batch_table VALUES (10,'Baen','M',DATE '1990-10-05',1010)" + "INSERT INTO unittest_namespace.batch_table VALUES (11,'Ricardo','M',DATE '1986-12-12',1011)" + "INSERT INTO unittest_namespace.batch_table VALUES (12,'Tim','M',DATE '1993-02-02',1012)" + "INSERT INTO unittest_namespace.batch_table VALUES (13,'Beya','F',DATE '1994-05-25',1013)" + }; + counts = testCase.conn.queryBatch("sql","unittest_namespace",queries); + testCase.verifyEqual(height(counts),13); + disp(counts) + testCase.verifyTrue(all(counts.RowsAffected == 1)); + r = testCase.conn.query("sql","unittest_namespace","SELECT COUNT(*) FROM unittest_namespace.batch_table"); + testCase.verifyEqual(r,13); + end + + function testBatchRollbackOnFailure(testCase) + queries = { + "INSERT INTO unittest_namespace.batch_table VALUES (1,'Alice','F',DATE '1990-01-15',1001)" + "BROKEN QUERY" + }; + testCase.verifyError(@() testCase.conn.queryBatch("sql","unittest_namespace",queries),?MException); + r = testCase.conn.query("sql","unittest_namespace","SELECT * FROM unittest_namespace.batch_table"); + testCase.verifyEmpty(r); + end + + function testSyntaxError(testCase) + testCase.verifyError(@() testCase.conn.query("sql","unittest_namespace","SELEC WRONG FROM nowhere"),?MException); + end + + + function testQueryWithSpaces(testCase) + % Insert with leading spaces + testCase.conn.query("sql","unittest_namespace", ... + " INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + testCase.conn.query("sql","unittest_namespace", ... + " INSERT INTO unittest_namespace.unittest_table VALUES (2,'Bob')"); + + r = testCase.conn.query("sql","unittest_namespace", ... + "SELECT id,name FROM unittest_namespace.unittest_table ORDER BY id"); + + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(r.Properties.VariableNames,{'id','name'}); + testCase.verifyEqual(height(r),2); + testCase.verifyEqual(table2cell(r(1,:)),{1,'Alice'}); + testCase.verifyEqual(table2cell(r(2,:)),{2,'Bob'}); + end + + function testConnectionFailure(testCase) + testCase.verifyError(@() ... + polypheny.Polypheny("localhost",9999,"pa","").query("sql","unittest_namespace","SELECT 1"), ... + ?MException); + end + + function testCommitFailureRollback(testCase) + queries = { + "INSERT INTO unittest_namespace.batch_table VALUES (1,'Alice','F',DATE '1990-01-15',1001)" + "Intentional nonsense to produce a failure" + }; + testCase.verifyError(@() ... + testCase.conn.queryBatch("sql","unittest_namespace",queries),?MException); + + r = testCase.conn.query("sql","unittest_namespace", ... + "SELECT * FROM unittest_namespace.batch_table"); + testCase.verifyEmpty(r); + end + + end + +end \ No newline at end of file diff --git a/app/src/main/java/polyphenyconnector/QueryExecutor.java b/app/src/main/java/polyphenyconnector/QueryExecutor.java index 4d65dd8..5a4660e 100644 --- a/app/src/main/java/polyphenyconnector/QueryExecutor.java +++ b/app/src/main/java/polyphenyconnector/QueryExecutor.java @@ -76,7 +76,7 @@ public Object execute( String language, String namespace, String query ) { // Unwrap Connection connection to the JDBC Driver-supplied PolyConnection polyConnection PolyConnection polyConnection = connection.unwrap( PolyConnection.class ); - // Create a PolyStatement object to call .execute(...) method of the JDBC- Driver on + // Create a PolyStatement object to call .execute(...) method of the JDBC-Driver on PolyStatement polyStatement = polyConnection.createPolyStatement(); // Call the execute(...) function on the polyStatement @@ -122,7 +122,7 @@ public Object execute( String language, String namespace, String query ) { * @return List result A list of integers, where the i-th entry will denote for the i-th query how many rows were touched, e.g. * n: n rows were updated, 0: no rows were touched. */ - public List executeBatchSql( List queries ) { + public int[] executeBatchSql( List queries ) { polyconnection.openIfNeeded(); try { polyconnection.beginTransaction(); @@ -134,14 +134,9 @@ public List executeBatchSql( List queries ) { } stmt.addBatch( query ); } - int[] resultArray = stmt.executeBatch(); + int[] result = stmt.executeBatch(); polyconnection.commitTransaction(); - - List result = new ArrayList<>( resultArray.length ); - for ( int r : resultArray ) { - result.add( r ); - } - return result; + return result; // return directly } catch ( SQLException e ) { try { polyconnection.rollbackTransaction(); @@ -268,7 +263,7 @@ private List DocumentToMatlab( DocumentResult documentResult ) { Iterator documentIterator = documentResult.iterator(); while ( documentIterator.hasNext() ) { PolyDocument document = documentIterator.next(); - docs.add( NestedPolyDocumentToString( document ) ); + docs.add( NestedPolyDocumentToString( document ) ); // at the most outer layer everything must be wrapped as PolyDocument } return docs; } @@ -290,7 +285,7 @@ private String NestedPolyDocumentToString( PolyDocument document ) { } - private String NestedListToString( Array array ) { + private String NestedArrayToString( Array array ) { StringBuilder sb = new StringBuilder(); sb.append( "[" ); try { @@ -319,7 +314,7 @@ private String anyToJson( Object result ) { case DOCUMENT: return NestedPolyDocumentToString( value.asDocument() ); case LIST: - return NestedListToString( value.asArray() ); + return NestedArrayToString( value.asArray() ); case BOOLEAN: return String.valueOf( value.asBoolean() ); case INTEGER: @@ -354,7 +349,7 @@ private String anyToJson( Object result ) { } else if ( result instanceof PolyDocument ) { return NestedPolyDocumentToString( (PolyDocument) result ); } else if ( result instanceof Array ) { - return NestedListToString( (Array) result ); + return NestedArrayToString( (Array) result ); } else if ( result instanceof String ) { return "\"" + escapeJson( (String) result ) + "\""; } else if ( result instanceof java.sql.Date diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java index 497bb82..7052cec 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestMQL.java @@ -58,7 +58,29 @@ void clearCollectionAfter() { } - @SuppressWarnings("unchecked") + @Test + void testDeleteManyRemovesAllDocs() { + // Arrange: create namespace & collection + myconnection.openIfNeeded(); + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.drop()" ); + myexecutor.execute( "mongo", "mongotest", "db.createCollection(\"unittest_collection\")" ); + + // Insert 3 docs + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"id\":1,\"name\":\"Alice\"})" ); + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"id\":2,\"name\":\"Bob\"})" ); + myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.insertOne({\"id\":3,\"name\":\"Ciri\"})" ); + + // Act: delete all + Object ack = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.deleteMany({})" ); + + // Assert: ack JSON contains deletedCount:3 + assertTrue( ack.toString().contains( "\"updateCount\":3" ), "Expected 3 deletions, got: " + ack ); + // Verify collection is empty + @SuppressWarnings("unchecked") List docs = (List) myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); + assertEquals( 0, docs.size(), "Collection should be empty after deleteMany({})" ); + } + + @Test void testInsertandDrop() { Object result = myexecutor.execute( "mongo", "mongotest", "db.unittest_collection.find({})" ); diff --git a/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java index e9d0378..d55df42 100644 --- a/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java +++ b/app/src/test/java/polyphenyconnector/QueryExecutorTestSQL.java @@ -244,10 +244,10 @@ void testBatchInsertEmployees() { ); // Do the batch execution using executeBatch(...) - List counts = myexecutor.executeBatchSql( queries ); + int[] counts = myexecutor.executeBatchSql( queries ); // Test that the length of the counts vector is 13 (for 13 queries in the queries list). - assertEquals( 13, counts.size(), "Batch should return 13 results" ); + assertEquals( 13, counts.length, "Batch should return 13 results" ); // Test the i-th entry in the counts vector is actually 1 (because the i-th query changed exactly 1 row) for ( Object c : counts ) { @@ -270,7 +270,7 @@ void testBatchRollbackOnFailure() { // Prepare one correct and one ill posed SQL statement to query as batch later. List queries = Arrays.asList( "INSERT INTO unittest_namespace.batch_table VALUES (1, 'Alice')", - "Purposefully messed up query message to produce a failure" // duplicate PK + "Purposefully messed up query message to produce a failure" // PK violation → id missing ); // Run the ill posed batch query and test an exception is thrown. @@ -311,7 +311,7 @@ void testSyntaxError() { void testCommitFailureRollback() { List queries = Arrays.asList( "INSERT INTO unittest_namespace.batch_table VALUES (1, 'Alice', 'F', DATE '1990-01-15', 1001)", - "Intentional nonsense to produce a failure" // PK violation + "Intentional nonsense to produce a failure" // PK violation → id missing ); assertThrows( RuntimeException.class, () -> { diff --git a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml new file mode 100644 index 0000000..7f4bf7d --- /dev/null +++ b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssp.xml new file mode 100644 index 0000000..bd593f9 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml new file mode 100644 index 0000000..59ecbe4 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgp.xml new file mode 100644 index 0000000..f66df9c --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsp.xml new file mode 100644 index 0000000..7b74471 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml new file mode 100644 index 0000000..d41b43c --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgp.xml new file mode 100644 index 0000000..17c5535 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml new file mode 100644 index 0000000..093b0f1 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml new file mode 100644 index 0000000..78c34ff --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3od.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3od.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3od.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3op.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3op.xml new file mode 100644 index 0000000..77329db --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3op.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4d.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4p.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4p.xml new file mode 100644 index 0000000..4137335 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml new file mode 100644 index 0000000..90cf55f --- /dev/null +++ b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml new file mode 100644 index 0000000..093b0f1 --- /dev/null +++ b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVId.xml b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVId.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVId.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml new file mode 100644 index 0000000..cddb21b --- /dev/null +++ b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEd.xml b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEp.xml b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8d.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8p.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8p.xml new file mode 100644 index 0000000..2488db0 --- /dev/null +++ b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml new file mode 100644 index 0000000..243370a --- /dev/null +++ b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml new file mode 100644 index 0000000..a89769e --- /dev/null +++ b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml new file mode 100644 index 0000000..8d43933 --- /dev/null +++ b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml new file mode 100644 index 0000000..782a920 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml new file mode 100644 index 0000000..c3f8439 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUd.xml new file mode 100644 index 0000000..a928488 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUp.xml new file mode 100644 index 0000000..2ad958a --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml new file mode 100644 index 0000000..bc2cd34 --- /dev/null +++ b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMd.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMp.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMp.xml new file mode 100644 index 0000000..d868ce2 --- /dev/null +++ b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml new file mode 100644 index 0000000..7603ade --- /dev/null +++ b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYd.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYp.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYp.xml new file mode 100644 index 0000000..bbe3722 --- /dev/null +++ b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4d.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4d.xml new file mode 100644 index 0000000..6d1c43c --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4p.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4p.xml new file mode 100644 index 0000000..e993c77 --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml new file mode 100644 index 0000000..d47011f --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml new file mode 100644 index 0000000..91b0acc --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml new file mode 100644 index 0000000..6c16a34 --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml new file mode 100644 index 0000000..76301e1 --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Id.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Id.xml new file mode 100644 index 0000000..e228479 --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Id.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Ip.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Ip.xml new file mode 100644 index 0000000..958c22f --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Ip.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZod.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZod.xml new file mode 100644 index 0000000..b5689bd --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZod.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZop.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZop.xml new file mode 100644 index 0000000..ffb1fe8 --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencd.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencd.xml new file mode 100644 index 0000000..646977e --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencp.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencp.xml new file mode 100644 index 0000000..2e052d9 --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml new file mode 100644 index 0000000..c67e567 --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml new file mode 100644 index 0000000..880a245 --- /dev/null +++ b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0d.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0p.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0p.xml new file mode 100644 index 0000000..577c784 --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsId.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsId.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsId.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsIp.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsIp.xml new file mode 100644 index 0000000..451b12c --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTod.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTod.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTod.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTop.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTop.xml new file mode 100644 index 0000000..a1eb7e1 --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml new file mode 100644 index 0000000..a222278 --- /dev/null +++ b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/Project.xml b/resources/project/Project.xml new file mode 100644 index 0000000..62d05aa --- /dev/null +++ b/resources/project/Project.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml new file mode 100644 index 0000000..6524665 --- /dev/null +++ b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEId.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEId.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEId.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml new file mode 100644 index 0000000..0542d70 --- /dev/null +++ b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJId.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJId.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJId.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYd.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYp.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYp.xml new file mode 100644 index 0000000..c8d062d --- /dev/null +++ b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkd.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkp.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml new file mode 100644 index 0000000..b8bb14e --- /dev/null +++ b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml new file mode 100644 index 0000000..0e252ed --- /dev/null +++ b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8d.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8p.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8p.xml new file mode 100644 index 0000000..b0b0690 --- /dev/null +++ b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml new file mode 100644 index 0000000..1950410 --- /dev/null +++ b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4d.xml b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4p.xml b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4p.xml new file mode 100644 index 0000000..7603ade --- /dev/null +++ b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118d.xml b/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118p.xml b/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118p.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml new file mode 100644 index 0000000..5de8c3e --- /dev/null +++ b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml new file mode 100644 index 0000000..642c7d7 --- /dev/null +++ b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4d.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4d.xml new file mode 100644 index 0000000..fa0df97 --- /dev/null +++ b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4p.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4p.xml new file mode 100644 index 0000000..6664727 --- /dev/null +++ b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml new file mode 100644 index 0000000..cddb21b --- /dev/null +++ b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wd.xml b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wp.xml b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqId.xml b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqId.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqId.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqIp.xml b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqIp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAd.xml b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAp.xml b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAp.xml new file mode 100644 index 0000000..20ecdaf --- /dev/null +++ b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml new file mode 100644 index 0000000..5ee8d79 --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8p.xml new file mode 100644 index 0000000..2123321 --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsId.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsId.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsId.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsIp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsIp.xml new file mode 100644 index 0000000..97fdb5d --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml new file mode 100644 index 0000000..ec993e5 --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml new file mode 100644 index 0000000..4137335 --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml new file mode 100644 index 0000000..77329db --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml new file mode 100644 index 0000000..c2f7900 --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcp.xml new file mode 100644 index 0000000..948abad --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml new file mode 100644 index 0000000..91e2a89 --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml new file mode 100644 index 0000000..78c34ff --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml new file mode 100644 index 0000000..603491d --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml new file mode 100644 index 0000000..87445f7 --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml new file mode 100644 index 0000000..8f602f1 --- /dev/null +++ b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml new file mode 100644 index 0000000..85fc3d7 --- /dev/null +++ b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkd.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkd.xml new file mode 100644 index 0000000..79316c3 --- /dev/null +++ b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkp.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkp.xml new file mode 100644 index 0000000..a975559 --- /dev/null +++ b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMd.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMd.xml new file mode 100644 index 0000000..7688e41 --- /dev/null +++ b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMp.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMp.xml new file mode 100644 index 0000000..95b7bfa --- /dev/null +++ b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml b/resources/project/root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml new file mode 100644 index 0000000..fee2cd2 --- /dev/null +++ b/resources/project/root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml b/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml new file mode 100644 index 0000000..471aaf8 --- /dev/null +++ b/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml b/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml new file mode 100644 index 0000000..2037c33 --- /dev/null +++ b/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml b/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml new file mode 100644 index 0000000..68eaea0 --- /dev/null +++ b/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml b/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml new file mode 100644 index 0000000..4ad31d1 --- /dev/null +++ b/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml b/resources/project/root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml new file mode 100644 index 0000000..a4de013 --- /dev/null +++ b/resources/project/root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml b/resources/project/root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml new file mode 100644 index 0000000..8b0d336 --- /dev/null +++ b/resources/project/root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml b/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml new file mode 100644 index 0000000..0bd8f41 --- /dev/null +++ b/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml b/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml new file mode 100644 index 0000000..ce65455 --- /dev/null +++ b/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/rootp.xml b/resources/project/rootp.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/rootp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml b/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml b/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqId.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqId.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqId.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqIp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqIp.xml new file mode 100644 index 0000000..7b74471 --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4d.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4d.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4d.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4p.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4p.xml new file mode 100644 index 0000000..d41b43c --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84d.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84p.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84p.xml new file mode 100644 index 0000000..4137335 --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml new file mode 100644 index 0000000..77329db --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml new file mode 100644 index 0000000..78c34ff --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkp.xml new file mode 100644 index 0000000..bd593f9 --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYp.xml new file mode 100644 index 0000000..90cf55f --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMp.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/toolbox.ignore b/toolbox.ignore new file mode 100644 index 0000000..d5161ed --- /dev/null +++ b/toolbox.ignore @@ -0,0 +1,32 @@ +% List the files in your toolbox folder to exclude from packaging. Specify the +% file path as a path relative to the toolbox folder. +% List only one exclude per line. +% +% For example: +% +% Exclude a specific file in the toolbox folder: +% file1.svn +% +% Exclude a specific file in a subfolder of the toolbox folder: +% example/file1.svn +% +% Exclude all files in a subfolder of the toolbox folder: +% example/* +% +% Exclude all files with a certain name in all subfolders of the toolbox folder: +% **/file1.svn +% +%Exclude all files matching a pattern in all subfolders of the toolbox folder: +% **/*.bak +% +% Exclude all top level files and folders beginning with the character "%": +% \%example/%file.svn +% +**/resources/project/**/* +resources +**/*.prj +**/*.prj.bak +**/.git/**/* +**/.svn/**/* +**/.buildtool/**/* +**/*.asv \ No newline at end of file From bbf434a2ffe982cf18ade8816d246f6e6d057d66 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 4 Oct 2025 23:21:53 +0200 Subject: [PATCH 39/44] Made the files in the connector and the src consistent --- app/bin/main/PolyphenyWrapperTest.m | 3 +- app/bin/main/PolyphenyWrapperTestSQL.m | 1 + app/src/main/java/PolyphenyWrapperTest.m | 3 +- app/src/main/java/PolyphenyWrapperTestSQL.m | 1 + app/src/main/java/QuickTest.java | 51 --------------------- 5 files changed, 4 insertions(+), 55 deletions(-) delete mode 100644 app/src/main/java/QuickTest.java diff --git a/app/bin/main/PolyphenyWrapperTest.m b/app/bin/main/PolyphenyWrapperTest.m index 3952634..68b1909 100644 --- a/app/bin/main/PolyphenyWrapperTest.m +++ b/app/bin/main/PolyphenyWrapperTest.m @@ -5,7 +5,6 @@ methods (TestMethodSetup) function setupConnection(testCase) - startup; testCase.conn = polypheny.Polypheny('localhost', int32(20590), 'pa', '' ); end end @@ -58,7 +57,7 @@ function testBatchInsert(testCase) result = testCase.conn.queryBatch("sql" , "" , queries); % Verify JDBC return codes - testCase.verifyEqual(result, [1 1]); + testCase.verifyEqual(result.RowsAffected', [1 1]); % Verify table contents T = testCase.conn.query("sql" , "" ,"SELECT id, name FROM batch_test ORDER BY id"); diff --git a/app/bin/main/PolyphenyWrapperTestSQL.m b/app/bin/main/PolyphenyWrapperTestSQL.m index 0e11906..0b25e6b 100644 --- a/app/bin/main/PolyphenyWrapperTestSQL.m +++ b/app/bin/main/PolyphenyWrapperTestSQL.m @@ -5,6 +5,7 @@ methods(TestClassSetup) function setUpNamespaceAndTable(testCase) + clc; % Open connection once for all tests testCase.conn = polypheny.Polypheny("localhost",20590,"pa",""); diff --git a/app/src/main/java/PolyphenyWrapperTest.m b/app/src/main/java/PolyphenyWrapperTest.m index 3952634..68b1909 100644 --- a/app/src/main/java/PolyphenyWrapperTest.m +++ b/app/src/main/java/PolyphenyWrapperTest.m @@ -5,7 +5,6 @@ methods (TestMethodSetup) function setupConnection(testCase) - startup; testCase.conn = polypheny.Polypheny('localhost', int32(20590), 'pa', '' ); end end @@ -58,7 +57,7 @@ function testBatchInsert(testCase) result = testCase.conn.queryBatch("sql" , "" , queries); % Verify JDBC return codes - testCase.verifyEqual(result, [1 1]); + testCase.verifyEqual(result.RowsAffected', [1 1]); % Verify table contents T = testCase.conn.query("sql" , "" ,"SELECT id, name FROM batch_test ORDER BY id"); diff --git a/app/src/main/java/PolyphenyWrapperTestSQL.m b/app/src/main/java/PolyphenyWrapperTestSQL.m index 0e11906..0b25e6b 100644 --- a/app/src/main/java/PolyphenyWrapperTestSQL.m +++ b/app/src/main/java/PolyphenyWrapperTestSQL.m @@ -5,6 +5,7 @@ methods(TestClassSetup) function setUpNamespaceAndTable(testCase) + clc; % Open connection once for all tests testCase.conn = polypheny.Polypheny("localhost",20590,"pa",""); diff --git a/app/src/main/java/QuickTest.java b/app/src/main/java/QuickTest.java deleted file mode 100644 index a381a6d..0000000 --- a/app/src/main/java/QuickTest.java +++ /dev/null @@ -1,51 +0,0 @@ -import java.util.Arrays; - -import polyphenyconnector.PolyphenyConnection; -import polyphenyconnector.QueryExecutor; - -public class QuickTest { - - public static void main( String[] args ) throws Exception { - PolyphenyConnection conn = new PolyphenyConnection( "localhost", 20590, "pa", "" ); - try { - QueryExecutor exec = new QueryExecutor( conn ); - - // 1) Scalar smoke test - Object r1 = exec.execute( "sql", "", "SELECT 1 AS x" ); - System.out.println( "Scalar result: " + r1 ); - - // 2) Table smoke test - Object r2 = exec.execute( "sql", "", "SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4" ); - printTable( r2 ); - - // 3) First row from emps (1-row table) - Object r3 = exec.execute( "sql", "emps", "SELECT * FROM emps LIMIT 1" ); - System.out.println( "First row from emps:" ); - printTable( r3 ); - - // 4) Scalar from emps - Object r4 = exec.execute( "sql", "emps", "SELECT empid FROM emps LIMIT 1" ); - System.out.println( "First empid (scalar): " + r4 ); - - } finally { - conn.close(); - System.exit( 0 ); - } - } - - - static void printTable( Object r ) { - if ( r instanceof Object[] ) { - Object[] t = (Object[]) r; // { colNames, data } - String[] cols = (String[]) t[0]; - Object[][] data = (Object[][]) t[1]; - System.out.println( "Cols: " + Arrays.toString( cols ) ); - for ( Object[] row : data ) { - System.out.println( Arrays.toString( row ) ); - } - } else { - System.out.println( String.valueOf( r ) ); - } - } - -} From 9e964306c2c1252fa0c4e882cac5f10c11311e5b Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 20 Dec 2025 00:43:43 +0100 Subject: [PATCH 40/44] Added complete README.md, cleaned up repository, updated gitignore to remove some unnecessary files and added the source folder to package the toolbox which generates the PolyphenyConnector.mltbx file (see README.md) --- .gitignore | 22 +- README.md | 55 +++- .../+polypheny/Polypheny.m | 138 ++++++++++ matlab-polypheny-connector/ClassTest.m | 9 + matlab-polypheny-connector/LICENSE | 201 ++++++++++++++ .../PolyphenyWrapperTest.m | 69 +++++ .../PolyphenyWrapperTestMQL.m | 245 ++++++++++++++++++ .../PolyphenyWrapperTestSQL.m | 220 ++++++++++++++++ .../matlab-polypheny-connector.prj | 2 + matlab-polypheny-connector/ode_demo.m | 0 matlab-polypheny-connector/startup.m | 33 +++ matlab-polypheny-connector/toolbox.ignore | 32 +++ .../35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml | 2 - .../35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml | 2 - .../q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml | 2 - .../q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml | 2 - .../-7lw3W0RxTIVHffMmdu5nJIpdssd.xml | 6 - .../-7lw3W0RxTIVHffMmdu5nJIpdssp.xml | 2 - .../-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml | 2 - .../-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml | 2 - .../0D8h_eihs7G5arxD4hyQnS4bcwgd.xml | 2 - .../0D8h_eihs7G5arxD4hyQnS4bcwgp.xml | 2 - .../JX1bqBrXPl367ChfkEVPXC5BHlsd.xml | 6 - .../JX1bqBrXPl367ChfkEVPXC5BHlsp.xml | 2 - .../Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml | 6 - .../Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml | 2 - .../SWQui2F2vzpqtemNQS5lPS2aNsgd.xml | 2 - .../SWQui2F2vzpqtemNQS5lPS2aNsgp.xml | 2 - .../X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml | 2 - .../X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml | 2 - .../YIBBj8UPFRjwM860n51m88FAsPsd.xml | 2 - .../YIBBj8UPFRjwM860n51m88FAsPsp.xml | 2 - .../og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml | 2 - .../og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml | 2 - .../vAQG464ZlAetqjsF49GGb06XF3od.xml | 2 - .../vAQG464ZlAetqjsF49GGb06XF3op.xml | 2 - .../ycwBpgzd0N4JLCNuP64rppTLLz4d.xml | 2 - .../ycwBpgzd0N4JLCNuP64rppTLLz4p.xml | 2 - .../zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml | 6 - .../zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml | 2 - .../7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml | 2 - .../7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml | 2 - .../NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml | 2 - .../NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml | 2 - .../1pTU7g02qBsY8TI_s0SdV0qAGVId.xml | 2 - .../1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml | 2 - .../CblqjzyIIYW27RyXgKMjijiwmVEd.xml | 2 - .../CblqjzyIIYW27RyXgKMjijiwmVEp.xml | 2 - .../QY57lh1PuBv61TnyPsky3BtOVd8d.xml | 2 - .../QY57lh1PuBv61TnyPsky3BtOVd8p.xml | 2 - .../Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml | 6 - .../Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml | 2 - .../s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml | 2 - .../s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml | 2 - .../xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml | 2 - .../xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml | 2 - .../1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml | 2 - .../1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml | 2 - .../dpRIzUK051moAOA5OtMey_GCSjUd.xml | 2 - .../dpRIzUK051moAOA5OtMey_GCSjUp.xml | 2 - .../R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml | 2 - .../R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml | 2 - .../XMD_i55c50JsD1yXqTnVc5f2zzMd.xml | 2 - .../XMD_i55c50JsD1yXqTnVc5f2zzMp.xml | 2 - .../sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml | 2 - .../sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml | 2 - .../7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml | 2 - .../7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml | 2 - .../guZAfj9lbhvPokQd3ABhzUUeAcYd.xml | 2 - .../guZAfj9lbhvPokQd3ABhzUUeAcYp.xml | 2 - .../zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml | 2 - .../zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml | 2 - .../2kj09UetkV_lru3gvSPXnY6-nM4d.xml | 2 - .../2kj09UetkV_lru3gvSPXnY6-nM4p.xml | 2 - .../KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml | 2 - .../KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml | 2 - .../QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml | 2 - .../QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml | 2 - .../R1RggVhA72agIvELiuhWPRS8F0Id.xml | 2 - .../R1RggVhA72agIvELiuhWPRS8F0Ip.xml | 2 - .../aEHSZBIY-yve10yGis12Zr5DLZod.xml | 2 - .../aEHSZBIY-yve10yGis12Zr5DLZop.xml | 2 - .../j4xwF_j8iFTVayUMfxLgMnTbencd.xml | 2 - .../j4xwF_j8iFTVayUMfxLgMnTbencp.xml | 2 - .../r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml | 2 - .../r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml | 2 - .../9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml | 2 - .../9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml | 2 - .../DZm04spwjRy--my25c3HQijqtP0d.xml | 2 - .../DZm04spwjRy--my25c3HQijqtP0p.xml | 2 - .../MFn52BkouEopT6vbPU3zED0ZDsId.xml | 2 - .../MFn52BkouEopT6vbPU3zED0ZDsIp.xml | 2 - .../SXm5a-Het5-ZRrlKiXpnmegsBTod.xml | 2 - .../SXm5a-Het5-ZRrlKiXpnmegsBTop.xml | 2 - .../gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml | 2 - .../gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml | 2 - resources/project/Project.xml | 2 - .../GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml | 2 - .../GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml | 2 - .../PEXPsKGais8E1vYnZ-aE3yL8sEId.xml | 2 - .../PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml | 2 - .../qfVPavq7Uqg1pEisrDaCVbtvDJId.xml | 2 - .../qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml | 2 - .../TMZL23F8wOV5HqNkYCt0VinxHtYd.xml | 2 - .../TMZL23F8wOV5HqNkYCt0VinxHtYp.xml | 2 - .../cInDQx1HNLTcw32-hZCkckIZJTkd.xml | 2 - .../cInDQx1HNLTcw32-hZCkckIZJTkp.xml | 2 - .../o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml | 2 - .../o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml | 2 - .../A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml | 2 - .../A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml | 2 - .../J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml | 2 - .../J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml | 2 - .../M3GsPDq-ce6V_JJNmshvkjerLK8d.xml | 2 - .../M3GsPDq-ce6V_JJNmshvkjerLK8p.xml | 2 - .../ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml | 2 - .../ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml | 2 - .../Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml | 2 - .../Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml | 2 - .../v66rjJH86SBncOTEIRzsIn29ae4d.xml | 2 - .../v66rjJH86SBncOTEIRzsIn29ae4p.xml | 2 - .../lmBC8LfYX7EpuYL-T1EededA118d.xml | 2 - .../lmBC8LfYX7EpuYL-T1EededA118p.xml | 2 - .../NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml | 2 - .../NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml | 2 - .../quWI0YIIYG9pN30wY38QKH6nHv4d.xml | 2 - .../quWI0YIIYG9pN30wY38QKH6nHv4p.xml | 2 - .../3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml | 2 - .../3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml | 2 - .../VANTmkLQY6xmL2oTlIa7vq1J98wd.xml | 2 - .../VANTmkLQY6xmL2oTlIa7vq1J98wp.xml | 2 - .../FIvnojW6RLtZgaABMtVnpkJSJqId.xml | 2 - .../FIvnojW6RLtZgaABMtVnpkJSJqIp.xml | 2 - .../oiBudmzlq222MZ8BwkOnRAtvJDAd.xml | 2 - .../oiBudmzlq222MZ8BwkOnRAtvJDAp.xml | 2 - .../Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml | 2 - .../Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml | 2 - .../MDnWniV77enVhZz9_UXTaL0JBX8d.xml | 2 - .../MDnWniV77enVhZz9_UXTaL0JBX8p.xml | 2 - .../Q397CBsXzx2zKNQDxi16JP-aBsId.xml | 2 - .../Q397CBsXzx2zKNQDxi16JP-aBsIp.xml | 2 - .../QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml | 2 - .../QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml | 2 - .../QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml | 2 - .../QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml | 2 - .../TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml | 2 - .../TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml | 2 - .../XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml | 2 - .../XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml | 2 - .../_62woAnzhP2vhMa00Uh-rC4Awdcd.xml | 2 - .../_62woAnzhP2vhMa00Uh-rC4Awdcp.xml | 2 - .../aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml | 2 - .../aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml | 2 - .../bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml | 2 - .../bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml | 2 - .../qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml | 2 - .../qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml | 2 - .../zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml | 2 - .../zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml | 2 - .../Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml | 2 - .../Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml | 2 - .../QC--iM64JrOQuKvG1jexlQeIeLkd.xml | 2 - .../QC--iM64JrOQuKvG1jexlQeIeLkp.xml | 2 - .../_-JNOLDhfGqXArGMpy1suCtgWXMd.xml | 2 - .../_-JNOLDhfGqXArGMpy1suCtgWXMp.xml | 2 - .../root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml | 2 - .../root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml | 2 - .../root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml | 2 - .../root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml | 2 - .../root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml | 2 - .../root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml | 2 - .../root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml | 2 - .../root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml | 2 - .../root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml | 2 - resources/project/rootp.xml | 2 - .../mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml | 2 - .../mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml | 2 - .../-e4XloybOK7yGSjQwdC4NSCunqId.xml | 6 - .../-e4XloybOK7yGSjQwdC4NSCunqIp.xml | 2 - .../CT9Miou6DxD317AvUkG0AxeLQZ4d.xml | 6 - .../CT9Miou6DxD317AvUkG0AxeLQZ4p.xml | 2 - .../SyRNys_JvjAdM_6mzWaNDXtRV84d.xml | 2 - .../SyRNys_JvjAdM_6mzWaNDXtRV84p.xml | 2 - .../W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml | 2 - .../W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml | 2 - .../cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml | 2 - .../cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml | 2 - .../dHTYEePuadCCzyAuQBl7PQUyNNkd.xml | 6 - .../dHTYEePuadCCzyAuQBl7PQUyNNkp.xml | 2 - .../i6RsrbhqjXuacJMckiJ55cynMiYd.xml | 6 - .../i6RsrbhqjXuacJMckiJ55cynMiYp.xml | 2 - .../on5V8t1iGoKMy6XZAskaWOm3fDMd.xml | 2 - .../on5V8t1iGoKMy6XZAskaWOm3fDMp.xml | 2 - 193 files changed, 1017 insertions(+), 407 deletions(-) create mode 100644 matlab-polypheny-connector/+polypheny/Polypheny.m create mode 100644 matlab-polypheny-connector/ClassTest.m create mode 100644 matlab-polypheny-connector/LICENSE create mode 100644 matlab-polypheny-connector/PolyphenyWrapperTest.m create mode 100644 matlab-polypheny-connector/PolyphenyWrapperTestMQL.m create mode 100644 matlab-polypheny-connector/PolyphenyWrapperTestSQL.m create mode 100644 matlab-polypheny-connector/matlab-polypheny-connector.prj create mode 100644 matlab-polypheny-connector/ode_demo.m create mode 100644 matlab-polypheny-connector/startup.m create mode 100644 matlab-polypheny-connector/toolbox.ignore delete mode 100644 resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml delete mode 100644 resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml delete mode 100644 resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml delete mode 100644 resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssd.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssp.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgd.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgp.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsd.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsp.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgd.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgp.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsd.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsp.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3od.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3op.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4d.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4p.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml delete mode 100644 resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml delete mode 100644 resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml delete mode 100644 resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml delete mode 100644 resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml delete mode 100644 resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml delete mode 100644 resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVId.xml delete mode 100644 resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml delete mode 100644 resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEd.xml delete mode 100644 resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEp.xml delete mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8d.xml delete mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8p.xml delete mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml delete mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml delete mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml delete mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml delete mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml delete mode 100644 resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml delete mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml delete mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml delete mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUd.xml delete mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUp.xml delete mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml delete mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml delete mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMd.xml delete mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMp.xml delete mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml delete mode 100644 resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml delete mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml delete mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml delete mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYd.xml delete mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYp.xml delete mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml delete mode 100644 resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4d.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4p.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Id.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Ip.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZod.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZop.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencd.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencp.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml delete mode 100644 resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0d.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0p.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsId.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsIp.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTod.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTop.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml delete mode 100644 resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml delete mode 100644 resources/project/Project.xml delete mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml delete mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml delete mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEId.xml delete mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml delete mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJId.xml delete mode 100644 resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml delete mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYd.xml delete mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYp.xml delete mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkd.xml delete mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkp.xml delete mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml delete mode 100644 resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml delete mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml delete mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml delete mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml delete mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml delete mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8d.xml delete mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8p.xml delete mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml delete mode 100644 resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml delete mode 100644 resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml delete mode 100644 resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml delete mode 100644 resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4d.xml delete mode 100644 resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4p.xml delete mode 100644 resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118d.xml delete mode 100644 resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118p.xml delete mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml delete mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml delete mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4d.xml delete mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4p.xml delete mode 100644 resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml delete mode 100644 resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml delete mode 100644 resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wd.xml delete mode 100644 resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wp.xml delete mode 100644 resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqId.xml delete mode 100644 resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqIp.xml delete mode 100644 resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAd.xml delete mode 100644 resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAp.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8d.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8p.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsId.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsIp.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcd.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcp.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml delete mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml delete mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml delete mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml delete mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkd.xml delete mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkp.xml delete mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMd.xml delete mode 100644 resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMp.xml delete mode 100644 resources/project/root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml delete mode 100644 resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml delete mode 100644 resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml delete mode 100644 resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml delete mode 100644 resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml delete mode 100644 resources/project/root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml delete mode 100644 resources/project/root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml delete mode 100644 resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml delete mode 100644 resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml delete mode 100644 resources/project/rootp.xml delete mode 100644 resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml delete mode 100644 resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqId.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqIp.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4d.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4p.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84d.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84p.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkd.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkp.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYd.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYp.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMd.xml delete mode 100644 resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMp.xml diff --git a/.gitignore b/.gitignore index 5e22b48..898b0ad 100644 --- a/.gitignore +++ b/.gitignore @@ -10,19 +10,29 @@ build/ .DS_Store Thumbs.db -# Ignore lib folder except .jar files +# ignore lib folder except .jar files libs/* -# Ignore Gradle project-specific cache directory +# ignore Gradle project-specific cache directory .gradle -# Ignore Gradle build output directory +# ignore Gradle build output directory build + +# ignore resources +resources/project/ + +# ignore the presentation and report +project_presentation +project_report + # ignore the polypheny-all.jar and app.jar files app\build\libs # ignore the matlab add-on folder structure -matlab-polypheny-connector - -/matlab-polypheny-connector/Toolbox1/ \ No newline at end of file +matlab-polypheny-connector/jar/polypheny-all.jar +matlab-polypheny-connector/resources +matlab-polypheny-connector/libs/polypheny-jdbc-driver-2.3.jar +matlab-polypheny-connector/Toolbox1/PolyphenyConnector.mltbx +matlab-polypheny-connector/Toolbox1/release/*.mltbx diff --git a/README.md b/README.md index a1a8f00..7cbb773 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,58 @@ Addon for Matlab to connect and query a Polypheny database. @REQUIREMENTS: -polypheny.jar must be in the lib folder - JDK17 (newer versions don't support the MatlabEngine yet https://www.mathworks.com/support/requirements/language-interfaces.html) +Not included in this repository: +- polypheny.jar (see Build) +- polypheny-jdbc-driver-2.3.jar (see Build) +- The Polypheny server is NOT started automatically (see Tests) +- The MATLAB toolbox (.mltbx) within the matlab-polypheny-connector (see Matlab Package Toolbox) + Tests: -Before the entire Project can be built it is necessary to run the Polypheny Application on the Local machine. Matlab natively uses Java 8. Consequently the Add-on is written in Java 8, but the polypheny.jar in supplied in the latest version is run with Java 17. To avoid version conflicts it is necessary to run the Polypheny Application separately before building the tests, otherwise the build will fail. Automating this startup within the project was considered, but ultimately rejected due to cross-platform instability and environment-specific issues. \ No newline at end of file +Before the entire Project can be built it is necessary to run the Polypheny application on the local machine. Matlab natively uses Java 8. Consequently the Add-on is written in Java 8, but the polypheny.jar supplied in the latest version is run with Java 17. To avoid version conflicts it is necessary to run the Polypheny Application separately before building the tests, otherwise the build will fail. Automating this startup within the project was considered, but ultimately rejected due to cross-platform instability and environment-specific issues. + + +Build: +To build the project the following requirements must be satisfied: +1. The libs folder must contain + -polypheny-jdbc-driver-2.3.jar + -polypheny.jar + +2. The Polypheny application must be active and running on the local machine (see "Tests"). + +3. gradle must be installed and sufficiently configured + +4. MATLAB: R2023b or newer (uses built-in Java 8) + +5. Polypheny JDBC driver: v2.3 + +6. Polypheny server: tested with v1.9 + +Shipping the Connector (Package Toolbox): +The connector can be distributed using a PolyphenyConnector.mltbx file that is sent to the user (or published). To create the .mltbx file one must make use of the matlab-polypheny-connector project folder in this repository. + +1. Open Matlab (version see above) +2. go to "Home" +3. open "Add-Ons" and select "Package Toolbox" +4. select the matlab-polypheny-connector as folder and accept +5. select the project called "Toolbox1" +6. add the latest versions of the following .jar: + - polypheny-all.jar to the jar folder + - polypheny-jdbc-driver-2.3.jar to the libs folder +7. delete the PolyphenyConnector.mltbx file in Toolbox1/release (otherwise this is repackaged and bloats the new .mltbx file) + +8. Make sure the preview looks in order: in particular: + - Output file name should be: PolyphenyConnector.mltbx + - Output location should be: [project root]\Toolbox1\release + - Matlab Path should be "/" + - the Class Path should include: + jar/polypheny-all.jar + libs/polypheny-jdbc-driver-2.3.jar +9. Click "Package Toolbox": Matlab will generate a PolyphenyConnector.mltbx file and store it in Toolbox1/release + + + + + + diff --git a/matlab-polypheny-connector/+polypheny/Polypheny.m b/matlab-polypheny-connector/+polypheny/Polypheny.m new file mode 100644 index 0000000..dfd0721 --- /dev/null +++ b/matlab-polypheny-connector/+polypheny/Polypheny.m @@ -0,0 +1,138 @@ +classdef Polypheny < handle +% POLYPHENY MATLAB wrapper for the Polypheny Java connector. Wraps polyphenyconnector.PolyphenyConnection +% and polyphenyconnector.QueryExecutor to run queries from MATLAB + properties ( Access = private ) + polyConnection % Java PolyphenyConnection + queryExecutor % Java QueryExecutor + + end + + methods + + function PolyWrapper = Polypheny( host, port, user, password ) + % Polypheny( LANGUAGE, HOST, PORT, USER, PASSWORD ): Set up Java connection + executor + % LANGUAGE: The database language ( 'sql', 'mongo', 'cypher' ) + % HOST: Database host ( e.g. 'localhost' ) + % PORT: Database port ( integer ) + % USER: Username + % PASSWORD: Password + + % This makes sure that Matlab sees Java classes supplied by the .jar files in the Matlabtoolbox PolyphenyConnector.mtlbx + try + if ~polypheny.Polypheny.hasPolypheny( ) + startup( ); + end + PolyWrapper.polyConnection = javaObject( "polyphenyconnector.PolyphenyConnection",host, int32( port ), user, password ); + PolyWrapper.queryExecutor = javaObject( "polyphenyconnector.QueryExecutor", PolyWrapper.polyConnection ); + + catch ME %Matlab Exception + disp( "Error: " + ME.message ) + end + + end + + + + function matlab_result = query( PolyWrapper, language, namespace, queryStr ) + % query( POLYWRAPPER, QUERYSTR ): Execute query via QueryExecutor.java + % POLYWRAPPER: The PolyWrapper Matlab object + % LANGUAGE: The language of the query string -> SQL, mongo, Cypher + % QUERYSTR: The queryStr set by the user + % @return matlab_result: The result of the query -> return type differs for SQL,Mongo and Cypher + + + try + java_result = PolyWrapper.queryExecutor.execute( string( language ), string( namespace ), queryStr ); + + switch lower( language ) + case "sql" + if isempty( java_result ) + matlab_result = []; + elseif isscalar( java_result ) + matlab_result = java_result; + elseif isa( java_result,'java.lang.Object[]' ) && numel( java_result )==2 + tmp = cell( java_result ); + colNames = cell( tmp{1} ); + data = cell( tmp{2} ); + matlab_result = cell2table( data, 'VariableNames', colNames ); + else + matlab_result = []; + end + + case "mongo" + if isa( java_result, 'java.util.List' ) + % Current driver behavior: always returns List of JSON docs + matlab_result = string(java_result); + elseif isnumeric( java_result ) + % Not observed in current driver, but kept for forward compatibility + % (e.g. if Polypheny ever returns scalar counts directly) + matlab_result = java_result; + else + error( "Unexpected Mongo result type: %s", class( java_result ) ); + end + + case "cypher" + % TODO: integrate once Cypher executor is ready + error( "Cypher not supported yet." ); + + otherwise + error( "Unsupported language: %s", language ); + end + + catch ME + error( "Query execution failed: %s", ME.message ); + end + end + + function matlab_result = queryBatch( PolyWrapper, language, namespace, queryList ) + % queryBatch( POLYWRAPPER, QUERYLIST ): Execute batch of non-SELECT statements + % QUERYLIST: A cell array of SQL strings ( INSERT, UPDATE, DELETE, etc. ) + % + % Returns: int array with rows affected per statement + + if ~iscell( queryList ) + error( 'queryBatch expects a cell array of query strings' ); + end + + javaList = java.util.ArrayList(); + for i = 1:numel( queryList ) + javaList.add( string(queryList{i} ) ); + end + + switch lower(language) + case "sql" + java_result = PolyWrapper.queryExecutor.executeBatchSql( javaList ); + %matlab_result = double(java_result(:))'; + vals = double(java_result(:)); % convert Java int[] to MATLAB column vector + matlab_result = array2table(vals, 'VariableNames', {'RowsAffected'}); + + case "mongo" + java_result = PolyWrapper.queryExecutor.executeBatchMongo( string(namespace), javaList ); + matlab_result = string( java_result ); % outer list + + case "cypher" + error( "Batch execution for Cypher not yet implemented." ); + + otherwise + error( "Unsupported language: %s", language ); + end + + end + + function close( PolyWrapper ) + % close( POLYWRAPPER ): Close the Java connection + % POLYWRAPPER: The PolyWrapper Matlab object + PolyWrapper.polyConnection.close( ); + end + end + + methods ( Static ) + function flag = hasPolypheny( ) + % HASPOLYPHENY Returns true if Polypheny Java classes are available because the exist( 'polyphenyconnector.PolyphenyConnection','class' ) + % returns 8 if Matlab sees the Java class and 0 otherwise. + flag = ( exist( 'polyphenyconnector.PolyphenyConnection','class' ) == 8 ); + end + + end + +end diff --git a/matlab-polypheny-connector/ClassTest.m b/matlab-polypheny-connector/ClassTest.m new file mode 100644 index 0000000..47f32fc --- /dev/null +++ b/matlab-polypheny-connector/ClassTest.m @@ -0,0 +1,9 @@ +clear all; clear classes +%conn = javaObject('polyphenyconnector.PolyphenyConnection', 'localhost', int32(20590), 'pa', ''); +%exec = javaObject('polyphenyconnector.QueryExecutor', conn); + +%res = exec.execute('sql', 'SELECT 1 AS x'); +results = runtests('PolyphenyWrapperTest'); +disp(results) + +%disp(res); diff --git a/matlab-polypheny-connector/LICENSE b/matlab-polypheny-connector/LICENSE new file mode 100644 index 0000000..845ef47 --- /dev/null +++ b/matlab-polypheny-connector/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2025] [Fynn Gohlke] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/matlab-polypheny-connector/PolyphenyWrapperTest.m b/matlab-polypheny-connector/PolyphenyWrapperTest.m new file mode 100644 index 0000000..68b1909 --- /dev/null +++ b/matlab-polypheny-connector/PolyphenyWrapperTest.m @@ -0,0 +1,69 @@ +classdef PolyphenyWrapperTest < matlab.unittest.TestCase + properties + conn + end + + methods (TestMethodSetup) + function setupConnection(testCase) + testCase.conn = polypheny.Polypheny('localhost', int32(20590), 'pa', '' ); + end + end + + methods (TestMethodTeardown) + function closeConnection(testCase) + testCase.conn.close(); + end + end + + methods (Test) + function testScalar(testCase) + r = testCase.conn.query( "sql" , "" , "SELECT 1 AS x"); + testCase.verifyEqual(r, 1); + end + + function testTable(testCase) + testCase.conn.query("sql" , "" , "DROP TABLE IF EXISTS wrapper_test"); + testCase.conn.query("sql" , "" , "CREATE TABLE wrapper_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + testCase.conn.query("sql" , "" , "INSERT INTO wrapper_test VALUES (1,'Alice'),(2,'Bob')"); + + T = testCase.conn.query("sql" , "" , "SELECT * FROM wrapper_test ORDER BY id"); + + if istable(T) + % Expected: table output with column "name" + testCase.verifyEqual(T.name, {'Alice'; 'Bob'}); + elseif iscell(T) + % Fallback: check the raw cell contents + testCase.verifyEqual(T(:,2), {'Alice','Bob'}'); + else + testCase.verifyFail("Unexpected return type: " + class(T)); + end + end + + function testEmpty(testCase) + T = testCase.conn.query("sql" , "" , "SELECT * FROM wrapper_test WHERE id=999"); + testCase.verifyEmpty(T); + end + + function testBatchInsert(testCase) + % Prepare table + testCase.conn.query("sql" , "" , "DROP TABLE IF EXISTS batch_test"); + testCase.conn.query("sql" , "" , "CREATE TABLE batch_test (id INTEGER PRIMARY KEY, name VARCHAR)"); + + % Batch insert 2 rows + queries = { ... + "INSERT INTO batch_test VALUES (1,'Alice')", ... + "INSERT INTO batch_test VALUES (2,'Bob')" ... + }; + result = testCase.conn.queryBatch("sql" , "" , queries); + + % Verify JDBC return codes + testCase.verifyEqual(result.RowsAffected', [1 1]); + + % Verify table contents + T = testCase.conn.query("sql" , "" ,"SELECT id, name FROM batch_test ORDER BY id"); + testCase.verifyEqual(T.id, [1; 2]); + testCase.verifyEqual(string(T.name), ["Alice"; "Bob"]); + end + + end +end \ No newline at end of file diff --git a/matlab-polypheny-connector/PolyphenyWrapperTestMQL.m b/matlab-polypheny-connector/PolyphenyWrapperTestMQL.m new file mode 100644 index 0000000..e992fc1 --- /dev/null +++ b/matlab-polypheny-connector/PolyphenyWrapperTestMQL.m @@ -0,0 +1,245 @@ +classdef PolyphenyWrapperTestMQL < matlab.unittest.TestCase + properties + conn + end + + methods(TestClassSetup) + function setUpNamespaceAndCollection(testCase) + clc; + % open connection once + testCase.conn = polypheny.Polypheny("localhost",20590,"pa",""); + + % try create collection + try + testCase.conn.query("mongo","mongotest", ... + 'db.createCollection("unittest_collection")'); + catch + end + end + end + + methods(TestClassTeardown) + function tearDownNamespaceAndCollection(testCase) + try + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.drop()'); + catch + end + testCase.conn.close(); + end + end + + methods(TestMethodSetup) + function clearCollection(testCase) + try + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.deleteMany({})'); + catch + end + end + end + + methods(TestMethodTeardown) + function clearCollectionAfter(testCase) + try + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.deleteMany({})'); + catch + end + end + end + + methods(Test) + + function testDeleteManyRemovesAllDocs(testCase) + % Drop & recreate collection + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.drop()'); + testCase.conn.query("mongo","mongotest", 'db.createCollection("unittest_collection")'); + + % Insert three documents + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.insertOne({"id":1,"name":"Alice"})'); + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.insertOne({"id":2,"name":"Bob"})'); + testCase.conn.query("mongo","mongotest", 'db.unittest_collection.insertOne({"id":3,"name":"Ciri"})'); + + % Call deleteMany({}) + ack = testCase.conn.query("mongo","mongotest", 'db.unittest_collection.deleteMany({})'); + disp("Ack from deleteMany:"); + disp(ack); + + % Verify collection is empty + docs = testCase.conn.query("mongo","mongotest", 'db.unittest_collection.find({})'); + docs = jsondecode(docs); + testCase.verifyEmpty(docs, "Collection should be empty after deleteMany({})"); + end + + function testInsertManyAndNestedDocument(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":14})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":20})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":24})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":30,"adress":{"Country":"Switzerland","Code":4051}})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({"age":{$gt:29}})'); + disp(docs) + decoded = jsondecode(docs); + disp(decoded) + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"age":30')); + end + + function testBooleanField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"flag":true})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + decoded = jsondecode(docs); + disp(decoded) + testCase.verifyTrue(contains(docs(1),'"flag":true')); + testCase.verifyClass(decoded.flag, 'logical'); % asserts that class(decoded.flag) == logical + end + + function testIntegerAgeField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"age":42})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"age":42')); + end + + function testStringField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"name":"Alice"})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"name":"Alice"')); + end + + function testLongField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"big":1111111111111111111})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"big":1111111111111111111')); + end + + function testDoubleField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"pi":3.14159})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'"pi":3.14159')); + end + + function testInsertAndQueryTwoDocsRawJson(testCase) + % Clean collection + testCase.conn.query("mongo","mongotest",'db.unittest_collection.drop()'); + testCase.conn.query("mongo","mongotest",'db.createCollection("unittest_collection")'); + + % Insert two docs + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.insertOne({"id":1,"name":"Alice"})'); + testCase.conn.query("mongo","mongotest", ... + 'db.unittest_collection.insertOne({"id":2,"name":"Bob"})'); + + % Query back + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp("Raw JSON:"); + disp(docs); + decoded = jsondecode(docs); + disp(decoded) + + % Assert raw JSON is exactly what we want + testCase.verifyTrue(contains(docs(1),'"name":"Alice"')); + testCase.verifyTrue(contains(docs(1),'"name":"Bob"')); + end + + + function testCountDocuments(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"name":"Bob"})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.countDocuments({})'); + disp(docs) + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs(1),'{"count":1}')); + end + + function testArrayField(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"scores":[1,2,3]})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyEqual(numel(docs),1); + testCase.verifyTrue(contains(docs,'"scores":[1,2,3]')); + end + + function testFindOnEmptyCollection(testCase) + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyEqual(docs,"[]"); + end + + function testInsertManyAndFindMultiple(testCase) + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"id":10,"name":"A"})'); + testCase.conn.query("mongo","mongotest",'db.unittest_collection.insertOne({"id":11,"name":"B"})'); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyTrue(contains(docs,'"id":10')) + testCase.verifyTrue(contains(docs,'"name":"A"')) + testCase.verifyTrue(contains(docs,'"id":11')) + testCase.verifyTrue(contains(docs,'"name":"B"')) + end + + function testBatchInsertAndFind(testCase) + queries = { ... + 'db.unittest_collection.insertOne({"name":"Alice","age":25})', ... + 'db.unittest_collection.insertOne({"name":"Alice","age":20})', ... + 'db.unittest_collection.insertOne({"name":"Bob","age":30})' }; + ignore = testCase.conn.queryBatch("mongo","mongotest",queries); + queries2 = { ... + 'db.unittest_collection.find({"name":"Alice"})', ... + 'db.unittest_collection.find({"name":"Alice","age":20})', ... + 'db.unittest_collection.find({"name":"Bob","age":30})' }; + docs = testCase.conn.queryBatch("mongo","mongotest", queries2); + disp(docs) + decoded = jsondecode(docs); + disp(decoded) + testCase.verifyEqual(numel(decoded{1}), 2); % 2 docs in first query + + % check names + names = {decoded{1}.name}; % cell of names + disp(names) + testCase.verifyEqual(string(names), ["Alice","Alice"]); + + end + + + function testBatchMixedOps(testCase) + queries = { ... + 'db.unittest_collection.insertOne({"name":"Charlie","active":true})', ... + 'db.unittest_collection.countDocuments({})' }; + docs = testCase.conn.queryBatch("mongo","mongotest",queries); + testCase.verifyEqual(numel(docs),1); + decoded = jsondecode(docs) + varname = fieldnames(decoded{2}) + disp(decoded{2}.count) + testCase.verifyTrue(decoded{2}.count==1); + end + + function testSyntaxErrorThrows(testCase) + badQuery = 'db.unittest_collection.insertOne({"foo":123)'; % invalid JSON + testCase.verifyError(@() testCase.conn.query("mongo","mongotest",badQuery),?MException); + end + + function testMultiStatementFails(testCase) + badMulti = [ ... + 'db.people.insertOne({"name":"Alice","age":20}); ' ... + 'db.people.insertOne({"name":"Bob","age":24}); ' ... + 'db.people.find({})' ]; + testCase.verifyError(@() testCase.conn.query("mongo","mongotest",badMulti),?MException); + end + + function testBatchRollback(testCase) + queries = { ... + 'db.unittest_collection.insertOne({"id":1,"name":"Alice"})', ... + 'db.unittest_collection.insertOne({"id":2,"name":"Bob"})', ... + 'db.unittest_collection.insertOne({"id":3,"name":"Janice"' }; % broken + testCase.verifyError(@() testCase.conn.queryBatch("mongo","mongotest",queries),?MException); + docs = testCase.conn.query("mongo","mongotest",'db.unittest_collection.find({})'); + disp(docs) + testCase.verifyEqual(numel(docs),1); + testCase.verifyEqual(docs,"[]") + end + + end +end diff --git a/matlab-polypheny-connector/PolyphenyWrapperTestSQL.m b/matlab-polypheny-connector/PolyphenyWrapperTestSQL.m new file mode 100644 index 0000000..0b25e6b --- /dev/null +++ b/matlab-polypheny-connector/PolyphenyWrapperTestSQL.m @@ -0,0 +1,220 @@ +classdef PolyphenyWrapperTestSQL < matlab.unittest.TestCase + properties + conn % polypheny.Polypheny wrapper + end + + methods(TestClassSetup) + function setUpNamespaceAndTable(testCase) + clc; + % Open connection once for all tests + testCase.conn = polypheny.Polypheny("localhost",20590,"pa",""); + + % Drop leftovers if they exist + try + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DROP NAMESPACE IF EXISTS unittest_namespace"); + catch + end + + % Create namespace + table for execute() + testCase.conn.query("sql","", ... + "CREATE NAMESPACE unittest_namespace"); + testCase.conn.query("sql","unittest_namespace", ... + "CREATE TABLE unittest_namespace.unittest_table (id INT NOT NULL, name VARCHAR(100), PRIMARY KEY(id))"); + + % Drop and recreate batch_table + try + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.batch_table"); + catch + end + testCase.conn.query("sql","unittest_namespace", ... + "CREATE TABLE unittest_namespace.batch_table (" + ... + "emp_id INT NOT NULL, " + ... + "name VARCHAR(100), " + ... + "gender VARCHAR(10), " + ... + "birthday DATE, " + ... + "employee_id INT, " + ... + "PRIMARY KEY(emp_id))"); + end + end + + methods(TestClassTeardown) + function tearDownNamespaceAndTable(testCase) + % Cleanup after all tests + try + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DROP TABLE IF EXISTS unittest_namespace.batch_table"); + testCase.conn.query("sql","", ... + "DROP NAMESPACE IF EXISTS unittest_namespace"); + catch + end + testCase.conn.close(); + end + end + + methods(TestMethodSetup) + function clearTables(testCase) + % Clear before each test + try + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.batch_table"); + catch + end + end + end + + methods(TestMethodTeardown) + function clearTablesAfter(testCase) + % Clear again after each test + try + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.unittest_table"); + testCase.conn.query("sql","unittest_namespace", ... + "DELETE FROM unittest_namespace.batch_table"); + catch + end + end + end + + methods(Test) + function testScalarLiteral(testCase) + r = testCase.conn.query("sql","","SELECT 42 AS answer"); + testCase.verifyEqual(r,42); + end + + function testEmptyLiteral(testCase) + r = testCase.conn.query("sql","","SELECT * FROM (SELECT 1) t WHERE 1=0"); + testCase.verifyEmpty(r); + end + + function testTableLiteral(testCase) + r = testCase.conn.query("sql","unittest_namespace","SELECT 1 AS a, 2 AS b UNION ALL SELECT 3,4"); + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(r.Properties.VariableNames,{'a','b'}); + testCase.verifyEqual(height(r),2); + testCase.verifyEqual(table2cell(r(1,:)),{1,2}); + testCase.verifyEqual(table2cell(r(2,:)),{3,4}); + end + + function testInsert(testCase) + r = testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + testCase.verifyEqual(r,1); + end + + function testInsertAndSelect(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + r = testCase.conn.query("sql","unittest_namespace","SELECT id,name FROM unittest_namespace.unittest_table"); + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(r.Properties.VariableNames,{'id','name'}); + testCase.verifyEqual(height(r),1); + testCase.verifyEqual(table2cell(r),{1,'Alice'}); + end + + function testScalarFromTable(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (2,'Carol')"); + r = testCase.conn.query("sql","unittest_namespace","SELECT id FROM unittest_namespace.unittest_table WHERE name='Carol'"); + testCase.verifyEqual(r,2); + end + + function testInsertAndSelectMultipleRows(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (2,'Bob')"); + r = testCase.conn.query("sql","unittest_namespace","SELECT id,name FROM unittest_namespace.unittest_table ORDER BY id"); + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(height(r),2); + testCase.verifyEqual(table2cell(r(1,:)),{1,'Alice'}); + testCase.verifyEqual(table2cell(r(2,:)),{2,'Bob'}); + end + + function testDeleteFromTable(testCase) + testCase.conn.query("sql","unittest_namespace","INSERT INTO unittest_namespace.unittest_table VALUES (2,'Bob')"); + testCase.conn.query("sql","unittest_namespace","DELETE FROM unittest_namespace.unittest_table"); + r = testCase.conn.query("sql","unittest_namespace","SELECT * FROM unittest_namespace.unittest_table"); + testCase.verifyEmpty(r); + end + + function testBatchInsertEmployees(testCase) + queries = { + "INSERT INTO unittest_namespace.batch_table VALUES (1,'Alice','F',DATE '1990-01-15',1001)" + "INSERT INTO unittest_namespace.batch_table VALUES (2,'Bob','M',DATE '1989-05-12',1002)" + "INSERT INTO unittest_namespace.batch_table VALUES (3,'Jane','F',DATE '1992-07-23',1003)" + "INSERT INTO unittest_namespace.batch_table VALUES (4,'Tim','M',DATE '1991-03-03',1004)" + "INSERT INTO unittest_namespace.batch_table VALUES (5,'Alex','M',DATE '1994-11-11',1005)" + "INSERT INTO unittest_namespace.batch_table VALUES (6,'Mason','M',DATE '1988-04-22',1006)" + "INSERT INTO unittest_namespace.batch_table VALUES (7,'Rena','F',DATE '1995-06-17',1007)" + "INSERT INTO unittest_namespace.batch_table VALUES (8,'Christopher','M',DATE '1987-08-09',1008)" + "INSERT INTO unittest_namespace.batch_table VALUES (9,'Lexi','F',DATE '1996-09-30',1009)" + "INSERT INTO unittest_namespace.batch_table VALUES (10,'Baen','M',DATE '1990-10-05',1010)" + "INSERT INTO unittest_namespace.batch_table VALUES (11,'Ricardo','M',DATE '1986-12-12',1011)" + "INSERT INTO unittest_namespace.batch_table VALUES (12,'Tim','M',DATE '1993-02-02',1012)" + "INSERT INTO unittest_namespace.batch_table VALUES (13,'Beya','F',DATE '1994-05-25',1013)" + }; + counts = testCase.conn.queryBatch("sql","unittest_namespace",queries); + testCase.verifyEqual(height(counts),13); + disp(counts) + testCase.verifyTrue(all(counts.RowsAffected == 1)); + r = testCase.conn.query("sql","unittest_namespace","SELECT COUNT(*) FROM unittest_namespace.batch_table"); + testCase.verifyEqual(r,13); + end + + function testBatchRollbackOnFailure(testCase) + queries = { + "INSERT INTO unittest_namespace.batch_table VALUES (1,'Alice','F',DATE '1990-01-15',1001)" + "BROKEN QUERY" + }; + testCase.verifyError(@() testCase.conn.queryBatch("sql","unittest_namespace",queries),?MException); + r = testCase.conn.query("sql","unittest_namespace","SELECT * FROM unittest_namespace.batch_table"); + testCase.verifyEmpty(r); + end + + function testSyntaxError(testCase) + testCase.verifyError(@() testCase.conn.query("sql","unittest_namespace","SELEC WRONG FROM nowhere"),?MException); + end + + + function testQueryWithSpaces(testCase) + % Insert with leading spaces + testCase.conn.query("sql","unittest_namespace", ... + " INSERT INTO unittest_namespace.unittest_table VALUES (1,'Alice')"); + testCase.conn.query("sql","unittest_namespace", ... + " INSERT INTO unittest_namespace.unittest_table VALUES (2,'Bob')"); + + r = testCase.conn.query("sql","unittest_namespace", ... + "SELECT id,name FROM unittest_namespace.unittest_table ORDER BY id"); + + testCase.verifyTrue(istable(r)); + testCase.verifyEqual(r.Properties.VariableNames,{'id','name'}); + testCase.verifyEqual(height(r),2); + testCase.verifyEqual(table2cell(r(1,:)),{1,'Alice'}); + testCase.verifyEqual(table2cell(r(2,:)),{2,'Bob'}); + end + + function testConnectionFailure(testCase) + testCase.verifyError(@() ... + polypheny.Polypheny("localhost",9999,"pa","").query("sql","unittest_namespace","SELECT 1"), ... + ?MException); + end + + function testCommitFailureRollback(testCase) + queries = { + "INSERT INTO unittest_namespace.batch_table VALUES (1,'Alice','F',DATE '1990-01-15',1001)" + "Intentional nonsense to produce a failure" + }; + testCase.verifyError(@() ... + testCase.conn.queryBatch("sql","unittest_namespace",queries),?MException); + + r = testCase.conn.query("sql","unittest_namespace", ... + "SELECT * FROM unittest_namespace.batch_table"); + testCase.verifyEmpty(r); + end + + end + +end \ No newline at end of file diff --git a/matlab-polypheny-connector/matlab-polypheny-connector.prj b/matlab-polypheny-connector/matlab-polypheny-connector.prj new file mode 100644 index 0000000..6b95f98 --- /dev/null +++ b/matlab-polypheny-connector/matlab-polypheny-connector.prj @@ -0,0 +1,2 @@ + + diff --git a/matlab-polypheny-connector/ode_demo.m b/matlab-polypheny-connector/ode_demo.m new file mode 100644 index 0000000..e69de29 diff --git a/matlab-polypheny-connector/startup.m b/matlab-polypheny-connector/startup.m new file mode 100644 index 0000000..c4dad7b --- /dev/null +++ b/matlab-polypheny-connector/startup.m @@ -0,0 +1,33 @@ +function startup + % Get root folder of the toolbox + root = fileparts(mfilename('fullpath')); + + % Paths to JARs + jarPaths = { ... + fullfile(root, 'jar', 'polypheny-all.jar'), ... + fullfile(root, 'libs', 'polypheny-jdbc-driver-2.3.jar') ... + }; + + % Add JARs if not already on classpath + for i = 1:numel(jarPaths) + if ~any(strcmp(jarPaths{i}, javaclasspath('-all'))) + javaaddpath(jarPaths{i}); + end + end + + % Try to register the JDBC driver dynamically + try + %java.lang.Class.forName('org.polypheny.jdbc.PolyphenyDriver'); + driver = javaObject('org.polypheny.jdbc.PolyphenyDriver'); + java.sql.DriverManager.registerDriver(driver); + catch e + warning('Could not register Polypheny JDBC driver dynamically: %s', char(e.message)); + end + + % Add MATLAB namespace folder (+polypheny) + if exist(fullfile(root, '+polypheny'), 'dir') + addpath(root); + end + + disp('Polypheny connector initialized.'); +end diff --git a/matlab-polypheny-connector/toolbox.ignore b/matlab-polypheny-connector/toolbox.ignore new file mode 100644 index 0000000..d5161ed --- /dev/null +++ b/matlab-polypheny-connector/toolbox.ignore @@ -0,0 +1,32 @@ +% List the files in your toolbox folder to exclude from packaging. Specify the +% file path as a path relative to the toolbox folder. +% List only one exclude per line. +% +% For example: +% +% Exclude a specific file in the toolbox folder: +% file1.svn +% +% Exclude a specific file in a subfolder of the toolbox folder: +% example/file1.svn +% +% Exclude all files in a subfolder of the toolbox folder: +% example/* +% +% Exclude all files with a certain name in all subfolders of the toolbox folder: +% **/file1.svn +% +%Exclude all files matching a pattern in all subfolders of the toolbox folder: +% **/*.bak +% +% Exclude all top level files and folders beginning with the character "%": +% \%example/%file.svn +% +**/resources/project/**/* +resources +**/*.prj +**/*.prj.bak +**/.git/**/* +**/.svn/**/* +**/.buildtool/**/* +**/*.asv \ No newline at end of file diff --git a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/35EjIrnXLDoNb0eoL5T0XQRn5m4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml b/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml deleted file mode 100644 index 7f4bf7d..0000000 --- a/resources/project/-Xk2hEuEOt19_hAdrnU8g93-E9U/q7NcH8v--t1JFVgHtoYnIfAiYJkp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssd.xml deleted file mode 100644 index 99772b4..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssd.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssp.xml deleted file mode 100644 index bd593f9..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-7lw3W0RxTIVHffMmdu5nJIpdssp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Ud.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml deleted file mode 100644 index 59ecbe4..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/-Xk2hEuEOt19_hAdrnU8g93-E9Up.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgp.xml deleted file mode 100644 index f66df9c..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/0D8h_eihs7G5arxD4hyQnS4bcwgp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsd.xml deleted file mode 100644 index 99772b4..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsd.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsp.xml deleted file mode 100644 index 7b74471..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/JX1bqBrXPl367ChfkEVPXC5BHlsp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml deleted file mode 100644 index 99772b4..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1od.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml deleted file mode 100644 index d41b43c..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/Kx4hz13Qli5r0yCtu2ut4ae-x1op.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgp.xml deleted file mode 100644 index 17c5535..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/SWQui2F2vzpqtemNQS5lPS2aNsgp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml deleted file mode 100644 index 093b0f1..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/X4Tac3t1B2PqVYeE44xIwuCsuG4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/YIBBj8UPFRjwM860n51m88FAsPsp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml deleted file mode 100644 index 78c34ff..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/og2jjaHXLTP-AsPOMZXSBYpM8SQp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3od.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3od.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3od.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3op.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3op.xml deleted file mode 100644 index 77329db..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/vAQG464ZlAetqjsF49GGb06XF3op.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4d.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4p.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4p.xml deleted file mode 100644 index 4137335..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/ycwBpgzd0N4JLCNuP64rppTLLz4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml deleted file mode 100644 index 99772b4..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Id.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml b/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml deleted file mode 100644 index 90cf55f..0000000 --- a/resources/project/1pTU7g02qBsY8TI_s0SdV0qAGVI/zmCkQIpQkykJF_LSYJ4D4Dk9S0Ip.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Md.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/7wbcFs1HrcXvTvRKfjiF3Elny5Mp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml b/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml deleted file mode 100644 index 093b0f1..0000000 --- a/resources/project/3SEiXPRwI8fadHpoAg2NZi6VJ48/NrBgY2I7zl9c-6Hntrc3SNmNkHEp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVId.xml b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVId.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVId.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml deleted file mode 100644 index cddb21b..0000000 --- a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/1pTU7g02qBsY8TI_s0SdV0qAGVIp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEd.xml b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEp.xml b/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/7P6Bq5OmzESd7MNZxRd0rLzR4SQ/CblqjzyIIYW27RyXgKMjijiwmVEp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8d.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8p.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8p.xml deleted file mode 100644 index 2488db0..0000000 --- a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/QY57lh1PuBv61TnyPsky3BtOVd8p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml deleted file mode 100644 index 243370a..0000000 --- a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjod.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml deleted file mode 100644 index a89769e..0000000 --- a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/Vl2kNS4BWPyyoXEhWvRtSDchmjop.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/s_XH-ClsEsyQHNbBwV4TO-2isqAp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml b/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml deleted file mode 100644 index 8d43933..0000000 --- a/resources/project/Dwzv2YWXcBI8xuJG5g6DCmXs6V4/xmqFNdZZ8woSkVcLHFjXWiNHndQp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml deleted file mode 100644 index 782a920..0000000 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml deleted file mode 100644 index c3f8439..0000000 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/1NcJ2QkXo1nNTjJLAEYM-fv27Zgp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUd.xml deleted file mode 100644 index a928488..0000000 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUp.xml deleted file mode 100644 index 2ad958a..0000000 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/dpRIzUK051moAOA5OtMey_GCSjUp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml deleted file mode 100644 index bc2cd34..0000000 --- a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/R2ZVqcWiaMalt1EAiI76Z-zvUZcp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMd.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMp.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMp.xml deleted file mode 100644 index d868ce2..0000000 --- a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/XMD_i55c50JsD1yXqTnVc5f2zzMp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml b/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/GFjlrB1KHq4DUqgJcBX8Hpcp33Q/sqnsBr-vzb8ZAkLH7E8_PBvckg0p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml deleted file mode 100644 index 7603ade..0000000 --- a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/7P6Bq5OmzESd7MNZxRd0rLzR4SQp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYd.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYp.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYp.xml deleted file mode 100644 index bbe3722..0000000 --- a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/guZAfj9lbhvPokQd3ABhzUUeAcYp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ed.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml b/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/M3GsPDq-ce6V_JJNmshvkjerLK8/zz2wvCKbG1A9qFkDj-p9UvKP54Ep.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4d.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4d.xml deleted file mode 100644 index 6d1c43c..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4p.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4p.xml deleted file mode 100644 index e993c77..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/2kj09UetkV_lru3gvSPXnY6-nM4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml deleted file mode 100644 index d47011f..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml deleted file mode 100644 index 91b0acc..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/KKyDJtbdIBOlaeHmIZd5VX6vqx8p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml deleted file mode 100644 index 6c16a34..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml deleted file mode 100644 index 76301e1..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/QWNDYJD5mGW1bWYvPx9DtKnxzw4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Id.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Id.xml deleted file mode 100644 index e228479..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Id.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Ip.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Ip.xml deleted file mode 100644 index 958c22f..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/R1RggVhA72agIvELiuhWPRS8F0Ip.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZod.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZod.xml deleted file mode 100644 index b5689bd..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZod.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZop.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZop.xml deleted file mode 100644 index ffb1fe8..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/aEHSZBIY-yve10yGis12Zr5DLZop.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencd.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencd.xml deleted file mode 100644 index 646977e..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencp.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencp.xml deleted file mode 100644 index 2e052d9..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/j4xwF_j8iFTVayUMfxLgMnTbencp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml deleted file mode 100644 index c67e567..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml b/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml deleted file mode 100644 index 880a245..0000000 --- a/resources/project/NjSPEMsIuLUyIpr2u1Js5bVPsOs/r8LR4nLmg9ai3oHrW1r_-KocQzkp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/9WHqp2R-_qgqzWRIL0jc0HabLQYp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0d.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0p.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0p.xml deleted file mode 100644 index 577c784..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/DZm04spwjRy--my25c3HQijqtP0p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsId.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsId.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsId.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsIp.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsIp.xml deleted file mode 100644 index 451b12c..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/MFn52BkouEopT6vbPU3zED0ZDsIp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTod.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTod.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTod.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTop.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTop.xml deleted file mode 100644 index a1eb7e1..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/SXm5a-Het5-ZRrlKiXpnmegsBTop.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ad.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml b/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml deleted file mode 100644 index a222278..0000000 --- a/resources/project/NrBgY2I7zl9c-6Hntrc3SNmNkHE/gTBh-4Ow-kKXXLbR0ifs3Lcd22Ap.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/Project.xml b/resources/project/Project.xml deleted file mode 100644 index 62d05aa..0000000 --- a/resources/project/Project.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml deleted file mode 100644 index 6524665..0000000 --- a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/GFjlrB1KHq4DUqgJcBX8Hpcp33Qp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEId.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEId.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEId.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml deleted file mode 100644 index 0542d70..0000000 --- a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/PEXPsKGais8E1vYnZ-aE3yL8sEIp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJId.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJId.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJId.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml b/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/QGtxt1WFaPeGWPm2FhsWdnKv8Lk/qfVPavq7Uqg1pEisrDaCVbtvDJIp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYd.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYp.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYp.xml deleted file mode 100644 index c8d062d..0000000 --- a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/TMZL23F8wOV5HqNkYCt0VinxHtYp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkd.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkp.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/cInDQx1HNLTcw32-hZCkckIZJTkp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml b/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml deleted file mode 100644 index b8bb14e..0000000 --- a/resources/project/X4Tac3t1B2PqVYeE44xIwuCsuG4/o1o4VAoIMeUvxjbDNIssXDOYzjgp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml deleted file mode 100644 index 0e252ed..0000000 --- a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/A5xSh7zwM9Q73Ead5ghol3kMEpMp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/J-hcGgzgSu60dB-FEwf4wwLC0Ycp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8d.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8p.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8p.xml deleted file mode 100644 index b0b0690..0000000 --- a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/M3GsPDq-ce6V_JJNmshvkjerLK8p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml b/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml deleted file mode 100644 index 1950410..0000000 --- a/resources/project/XWur0Y3G8SZkiCKxSbcf3hz8tHo/ZS4dWCzmfiCr764GIawNvmCdJ9cp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/Tt5X120o7Q3dK1u7Ciq7deJyTCsp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4d.xml b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4p.xml b/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4p.xml deleted file mode 100644 index 7603ade..0000000 --- a/resources/project/ZS4dWCzmfiCr764GIawNvmCdJ9c/v66rjJH86SBncOTEIRzsIn29ae4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118d.xml b/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118p.xml b/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118p.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/amwE3LmoG--0pRWluRol6WgE4ZY/lmBC8LfYX7EpuYL-T1EededA118p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml deleted file mode 100644 index 5de8c3e..0000000 --- a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml deleted file mode 100644 index 642c7d7..0000000 --- a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/NjSPEMsIuLUyIpr2u1Js5bVPsOsp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4d.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4d.xml deleted file mode 100644 index fa0df97..0000000 --- a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4p.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4p.xml deleted file mode 100644 index 6664727..0000000 --- a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/quWI0YIIYG9pN30wY38QKH6nHv4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml deleted file mode 100644 index cddb21b..0000000 --- a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/3SEiXPRwI8fadHpoAg2NZi6VJ48p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wd.xml b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wp.xml b/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/guZAfj9lbhvPokQd3ABhzUUeAcY/VANTmkLQY6xmL2oTlIa7vq1J98wp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqId.xml b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqId.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqId.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqIp.xml b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqIp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/FIvnojW6RLtZgaABMtVnpkJSJqIp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAd.xml b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAp.xml b/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAp.xml deleted file mode 100644 index 20ecdaf..0000000 --- a/resources/project/q7NcH8v--t1JFVgHtoYnIfAiYJk/oiBudmzlq222MZ8BwkOnRAtvJDAp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml deleted file mode 100644 index 5ee8d79..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Dwzv2YWXcBI8xuJG5g6DCmXs6V4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8p.xml deleted file mode 100644 index 2123321..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MDnWniV77enVhZz9_UXTaL0JBX8p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsId.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsId.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsId.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsIp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsIp.xml deleted file mode 100644 index 97fdb5d..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/Q397CBsXzx2zKNQDxi16JP-aBsIp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml deleted file mode 100644 index ec993e5..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QGtxt1WFaPeGWPm2FhsWdnKv8Lkp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml deleted file mode 100644 index 4137335..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/QMJD9OLFzxcTTbPOoh-ahQ4zTRUp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml deleted file mode 100644 index 77329db..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TMK4UzWHdRLhy_w-CHt9y11Q8XAp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHod.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml deleted file mode 100644 index c2f7900..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/XWur0Y3G8SZkiCKxSbcf3hz8tHop.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcp.xml deleted file mode 100644 index 948abad..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/_62woAnzhP2vhMa00Uh-rC4Awdcp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml deleted file mode 100644 index 91e2a89..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/aRiIAqJQdJmcMIFnpZqwW8oCyA8p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml deleted file mode 100644 index 78c34ff..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/bTEJeU_8R4R4qC77t7aJSjzV1F0p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml deleted file mode 100644 index 603491d..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/qD-kr16wmwlzR-nIg1IG_vvRrWkp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml deleted file mode 100644 index 87445f7..0000000 --- a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/zRAFHoWRV0Xdr6zRMi9A3_4a0u4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml deleted file mode 100644 index 8f602f1..0000000 --- a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Ud.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml deleted file mode 100644 index 85fc3d7..0000000 --- a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/Hcz0VKUHoSwUlR6n95PzTbwyQ-Up.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkd.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkd.xml deleted file mode 100644 index 79316c3..0000000 --- a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkp.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkp.xml deleted file mode 100644 index a975559..0000000 --- a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/QC--iM64JrOQuKvG1jexlQeIeLkp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMd.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMd.xml deleted file mode 100644 index 7688e41..0000000 --- a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMp.xml b/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMp.xml deleted file mode 100644 index 95b7bfa..0000000 --- a/resources/project/quWI0YIIYG9pN30wY38QKH6nHv4/_-JNOLDhfGqXArGMpy1suCtgWXMp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml b/resources/project/root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml deleted file mode 100644 index fee2cd2..0000000 --- a/resources/project/root/EEtUlUb-dLAdf0KpMVivaUlztwAp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml b/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml deleted file mode 100644 index 471aaf8..0000000 --- a/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml b/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml deleted file mode 100644 index 2037c33..0000000 --- a/resources/project/root/GiiBklLgTxteCEmomM8RCvWT0nQp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml b/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml deleted file mode 100644 index 68eaea0..0000000 --- a/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml b/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml deleted file mode 100644 index 4ad31d1..0000000 --- a/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml b/resources/project/root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml deleted file mode 100644 index a4de013..0000000 --- a/resources/project/root/fjRQtWiSIy7hIlj-Kmk87M7s21kp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml b/resources/project/root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml deleted file mode 100644 index 8b0d336..0000000 --- a/resources/project/root/qaw0eS1zuuY1ar9TdPn1GMfrjbQp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml b/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml deleted file mode 100644 index 0bd8f41..0000000 --- a/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml b/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml deleted file mode 100644 index ce65455..0000000 --- a/resources/project/root/u1WStHmjmq7I68B58slbv8Rm1_Qp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/rootp.xml b/resources/project/rootp.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/rootp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml b/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml b/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/u1WStHmjmq7I68B58slbv8Rm1_Q/mQajCe6YIEZsjlX5r0cW9Z0qnqcp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqId.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqId.xml deleted file mode 100644 index 99772b4..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqId.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqIp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqIp.xml deleted file mode 100644 index 7b74471..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/-e4XloybOK7yGSjQwdC4NSCunqIp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4d.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4d.xml deleted file mode 100644 index 99772b4..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4d.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4p.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4p.xml deleted file mode 100644 index d41b43c..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/CT9Miou6DxD317AvUkG0AxeLQZ4p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84d.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84d.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84d.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84p.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84p.xml deleted file mode 100644 index 4137335..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/SyRNys_JvjAdM_6mzWaNDXtRV84p.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml deleted file mode 100644 index 77329db..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/W7M8K4TnTaueRNaoQjTyZEf-uqUp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml deleted file mode 100644 index 78c34ff..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/cMoGqgeoIo7IeNMw43nOAm7Z_AAp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkd.xml deleted file mode 100644 index 99772b4..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkd.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkp.xml deleted file mode 100644 index bd593f9..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/dHTYEePuadCCzyAuQBl7PQUyNNkp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYd.xml deleted file mode 100644 index 99772b4..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYd.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYp.xml deleted file mode 100644 index 90cf55f..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/i6RsrbhqjXuacJMckiJ55cynMiYp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMd.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMd.xml deleted file mode 100644 index 4356a6a..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMd.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMp.xml b/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMp.xml deleted file mode 100644 index 01cb34e..0000000 --- a/resources/project/v66rjJH86SBncOTEIRzsIn29ae4/on5V8t1iGoKMy6XZAskaWOm3fDMp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file From b0b7f9aa6f47d2bebf6ec3f81e5e963403968659 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sat, 20 Dec 2025 22:16:28 +0100 Subject: [PATCH 41/44] split into developers documentation and users documentation, formatted the developers doc properly in markdown. --- README.md | 59 ----------------------- documentation/dev/README.md | 94 +++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 59 deletions(-) delete mode 100644 README.md create mode 100644 documentation/dev/README.md diff --git a/README.md b/README.md deleted file mode 100644 index 7cbb773..0000000 --- a/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Matlab-Connector -Addon for Matlab to connect and query a Polypheny database. - -@REQUIREMENTS: -JDK17 (newer versions don't support the MatlabEngine yet https://www.mathworks.com/support/requirements/language-interfaces.html) - -Not included in this repository: -- polypheny.jar (see Build) -- polypheny-jdbc-driver-2.3.jar (see Build) -- The Polypheny server is NOT started automatically (see Tests) -- The MATLAB toolbox (.mltbx) within the matlab-polypheny-connector (see Matlab Package Toolbox) - -Tests: -Before the entire Project can be built it is necessary to run the Polypheny application on the local machine. Matlab natively uses Java 8. Consequently the Add-on is written in Java 8, but the polypheny.jar supplied in the latest version is run with Java 17. To avoid version conflicts it is necessary to run the Polypheny Application separately before building the tests, otherwise the build will fail. Automating this startup within the project was considered, but ultimately rejected due to cross-platform instability and environment-specific issues. - - -Build: -To build the project the following requirements must be satisfied: -1. The libs folder must contain - -polypheny-jdbc-driver-2.3.jar - -polypheny.jar - -2. The Polypheny application must be active and running on the local machine (see "Tests"). - -3. gradle must be installed and sufficiently configured - -4. MATLAB: R2023b or newer (uses built-in Java 8) - -5. Polypheny JDBC driver: v2.3 - -6. Polypheny server: tested with v1.9 - -Shipping the Connector (Package Toolbox): -The connector can be distributed using a PolyphenyConnector.mltbx file that is sent to the user (or published). To create the .mltbx file one must make use of the matlab-polypheny-connector project folder in this repository. - -1. Open Matlab (version see above) -2. go to "Home" -3. open "Add-Ons" and select "Package Toolbox" -4. select the matlab-polypheny-connector as folder and accept -5. select the project called "Toolbox1" -6. add the latest versions of the following .jar: - - polypheny-all.jar to the jar folder - - polypheny-jdbc-driver-2.3.jar to the libs folder -7. delete the PolyphenyConnector.mltbx file in Toolbox1/release (otherwise this is repackaged and bloats the new .mltbx file) - -8. Make sure the preview looks in order: in particular: - - Output file name should be: PolyphenyConnector.mltbx - - Output location should be: [project root]\Toolbox1\release - - Matlab Path should be "/" - - the Class Path should include: - jar/polypheny-all.jar - libs/polypheny-jdbc-driver-2.3.jar -9. Click "Package Toolbox": Matlab will generate a PolyphenyConnector.mltbx file and store it in Toolbox1/release - - - - - - diff --git a/documentation/dev/README.md b/documentation/dev/README.md new file mode 100644 index 0000000..6aa1aab --- /dev/null +++ b/documentation/dev/README.md @@ -0,0 +1,94 @@ +--- +layout: page +title: "MATLAB Connector (Developers)" +toc: true +docs_area: "devs" +doc_type: doc +tags: Matlab Connector, Matlab Toolbox, Matlab, Connector +search: false +lang: en +--- + +# MATLAB Connector (Developer Documentation) + +This page documents developer-specific details for maintaining and extending the MATLAB Connector for Polypheny. + +## Requirements + +- **Java:** JDK 17 or older + (newer versions are not supported by the MATLAB Engine) + https://www.mathworks.com/support/requirements/language-interfaces.html +- **Polypheny:** must be running locally +- **MATLAB:** R2023b or newer (ships with Java 8) + +## Repository Contents + +The following components are **not included** in this repository: + +- `polypheny.jar` (see **Build**) +- `polypheny-jdbc-driver-2.3.jar` (see **Build**) +- Polypheny server startup (see **Tests**) +- Packaged MATLAB toolbox (`.mltbx`) (see **Packaging**) + +## Tests + +Before running or building tests, the Polypheny server must be started manually. + +MATLAB uses Java 8 internally, while the Polypheny server requires Java 17. +To avoid version conflicts, the Polypheny server is run **outside** the build process. + +Automatic startup was evaluated but rejected due to: +- cross-platform instability +- environment-specific Java configuration issues + +If the server is not running, the build will fail. + +## Build + +To build the project, ensure the following: + +1. The `libs/` directory contains: + - `polypheny.jar` + - `polypheny-jdbc-driver-2.3.jar` + +2. The Polypheny server is running locally. + +3. Gradle is installed and configured. + +4. Versions used: + - **MATLAB:** R2023b+ + - **Polypheny JDBC driver:** 2.3 + - **Polypheny server:** tested with 1.9 + +## Packaging (MATLAB Toolbox) + +The connector is distributed as a MATLAB toolbox (`.mltbx`). + +### Steps + +1. Open MATLAB +2. Go to **Home** +3. Open **Add-Ons → Package Toolbox** +4. Select the `matlab-polypheny-connector` folder +5. Select the project **Toolbox1** +6. Add JARs: + - `polypheny-all.jar` → `jar/` + - `polypheny-jdbc-driver-2.3.jar` → `libs/` +7. Delete any existing `PolyphenyConnector.mltbx` in `Toolbox1/release` + +### Packaging Settings + +Verify the preview: + +- **Output file:** `PolyphenyConnector.mltbx` +- **Output location:** `Toolbox1/release` +- **MATLAB path:** `/` +- **Class path:** + + jar/polypheny-all.jar + libs/polypheny-jdbc-driver-2.3.jar + + +8. Click **Package Toolbox** + +MATLAB will generate the toolbox file in `Toolbox1/release`. \ No newline at end of file From 0f8cad4c8f96fde0c082d77bca2cffd6ffe78c48 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sun, 21 Dec 2025 18:04:30 +0100 Subject: [PATCH 42/44] Setting up the User documentation. Pushing the first setup and handling instructions. Code example on the way. --- documentation/user/README.md | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 documentation/user/README.md diff --git a/documentation/user/README.md b/documentation/user/README.md new file mode 100644 index 0000000..3a7236f --- /dev/null +++ b/documentation/user/README.md @@ -0,0 +1,62 @@ +--- +layout: page +title: "MATLAB Connector (Users)" +toc: true +docs_area: "users" +doc_type: doc +tags: Matlab Connector, Matlab Toolbox, Matlab, Connector +search: true +lang: en +--- + +# MATLAB Connector (User Documentation) + +## Setting up your connector +The Polypheny-Matlab connector is supplied using a PolyphenyConnector.mltbx file that will automatically install the necessary package in your Matlab environment. To install it: +- open Matlab and wait until it is ready to run +- double-click the supplied PolyphenyConnector.mltbx and wait a few seconds. Matlab should inform you the package was successfully installed. + +> **Note** +> +> MATLAB provides a graphical UI to manage installed Add-Ons. In practice, this UI can be unreliable. +> If uninstalling the Polypheny Connector via the UI fails (e.g. when installing an updated version), +> the toolbox can be force-removed manually. + +## Manual Uninstall (Force Removal) + +### 1. Locate the installed toolbox + +Run the following in the MATLAB console: + + which polypheny.Polypheny -all + +This returns the folder where the toolbox is installed, typically of the form: + + \MathWorks\MATLAB Add-Ons\Toolboxes\PolyphenyConnector +> +>### 2. Delete the toolbox folder +> +>Delete the entire `PolyphenyConnector` directory from disk. +>If files are locked on Windows, close MATLAB first and then delete the folder. +> +>### 3. Remove the toolbox from the MATLAB search path + + rmpath('') + +### 4. Update MATLAB’s internal toolbox cache + + rehash toolboxcache + +### 5. Persist the updated search path + + savepath + +### Fallback (only if the path is badly polluted) + + restoredefaultpath + rehash toolboxcache + savepath + +This procedure bypasses MATLAB’s Add-On Manager and ensures the toolbox is fully removed +from disk, the search path, and MATLAB’s internal cache. Note the PolyphenyConnector might not show up in your UI anymore, even if installation was successful. + From c3dd2d3eca3feb63b40a0972b6448c75a2e69ec0 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Sun, 21 Dec 2025 20:10:01 +0100 Subject: [PATCH 43/44] Made a small addition to the dev documentation about how to compile the polypheny-all.jar and added SQL documentation for the users. --- documentation/dev/README.md | 11 ++-- documentation/user/README.md | 123 +++++++++++++++++++++++++++++++---- 2 files changed, 115 insertions(+), 19 deletions(-) diff --git a/documentation/dev/README.md b/documentation/dev/README.md index 6aa1aab..ca044cc 100644 --- a/documentation/dev/README.md +++ b/documentation/dev/README.md @@ -65,7 +65,7 @@ To build the project, ensure the following: The connector is distributed as a MATLAB toolbox (`.mltbx`). ### Steps - +0. create a `.jar` file with your latest version of the Polypheny Connector by compiling the project. (**Tip**: The compiled `polypheny-all.jar` will be stored under ) 1. Open MATLAB 2. Go to **Home** 3. Open **Add-Ons → Package Toolbox** @@ -76,19 +76,18 @@ The connector is distributed as a MATLAB toolbox (`.mltbx`). - `polypheny-jdbc-driver-2.3.jar` → `libs/` 7. Delete any existing `PolyphenyConnector.mltbx` in `Toolbox1/release` -### Packaging Settings - -Verify the preview: +8. Before we finally package the Toolbox you must verify the following are true: - **Output file:** `PolyphenyConnector.mltbx` - **Output location:** `Toolbox1/release` - **MATLAB path:** `/` -- **Class path:** +- **Class path must include:** jar/polypheny-all.jar + libs/polypheny-jdbc-driver-2.3.jar -8. Click **Package Toolbox** +9. Click **Package Toolbox** (top right corner in MATLAB) MATLAB will generate the toolbox file in `Toolbox1/release`. \ No newline at end of file diff --git a/documentation/user/README.md b/documentation/user/README.md index 3a7236f..8ee9598 100644 --- a/documentation/user/README.md +++ b/documentation/user/README.md @@ -22,9 +22,9 @@ The Polypheny-Matlab connector is supplied using a PolyphenyConnector.mltbx file > If uninstalling the Polypheny Connector via the UI fails (e.g. when installing an updated version), > the toolbox can be force-removed manually. -## Manual Uninstall (Force Removal) +### Manual Uninstall (Force Removal) -### 1. Locate the installed toolbox +#### 1. Locate the installed toolbox Run the following in the MATLAB console: @@ -33,30 +33,127 @@ Run the following in the MATLAB console: This returns the folder where the toolbox is installed, typically of the form: \MathWorks\MATLAB Add-Ons\Toolboxes\PolyphenyConnector -> ->### 2. Delete the toolbox folder -> ->Delete the entire `PolyphenyConnector` directory from disk. ->If files are locked on Windows, close MATLAB first and then delete the folder. -> ->### 3. Remove the toolbox from the MATLAB search path + +#### 2. Delete the toolbox folder + +Delete the entire `PolyphenyConnector` directory from disk. +If files are locked on Windows, close MATLAB first and then delete the folder. + +#### 3. Remove the toolbox from the MATLAB search path rmpath('') -### 4. Update MATLAB’s internal toolbox cache +#### 4. Update MATLAB’s internal toolbox cache rehash toolboxcache -### 5. Persist the updated search path +#### 5. Persist the updated search path savepath -### Fallback (only if the path is badly polluted) +#### Fallback (only if the path is badly polluted) restoredefaultpath rehash toolboxcache savepath This procedure bypasses MATLAB’s Add-On Manager and ensures the toolbox is fully removed -from disk, the search path, and MATLAB’s internal cache. Note the PolyphenyConnector might not show up in your UI anymore, even if installation was successful. +from disk, the search path, and MATLAB’s internal cache. Note the PolyphenyConnector might not show up in Matlabs Add-on UI anymore, even if the installation was successful. + + +## MATLAB Connector – Usage Examples +This section demonstrates how to use the Polypheny MATLAB connector. +All examples assume a local Polypheny instance running on port `20590`. + +--- +### Opening and Closing a Connection +Create a connection by specifying host, port, username, and password and close the connection afterwards +```matlab +conn = polypheny.Polypheny( 'host', 'port', 'username', 'password' ); +% your code goes here +conn.close(); +``` +To test this with your local machine as host we could do +```matlab +conn = polypheny.Polypheny( 'localhost', int32(20590), 'username', 'pa' ); +% your code goes here +conn.close(); +``` +### Executing queries +Queries are executed using +```matlab +result = conn.query( 'language', 'namespace', queryString ); +``` +where `language` is an element of `{'sql', 'mongo', 'cypher'}`, `'namespace'` is the name of the namespace the query targets in the database and `queryString` is the string passed to the database. + +> **Note:** +> For Mongo queries the namespace argument is necessary for the creation and deletion of data structures in the backend. For SQL queries the namespace argument has no consequence and thus does not need to be set. In the following examples we will therefore use `""` as namespace argument. + +### Executing SQL-queries +Let us look at some practical examples +```matlab +conn.query( "sql", "", "DROP TABLE IF EXISTS test" ); +conn.query( "sql", "", "CREATE TABLE test (id INTEGER PRIMARY KEY, name VARCHAR)" ); +conn.query( "sql", "", "INSERT INTO test VALUES (1,'Alice'),(2,'Bob')" ); +``` + +#### Scalar results +If a query returns a single value, the result is returned as a MATLAB scalar. +```matlab +x = conn.query( "sql", "", "SELECT COUNT(*) FROM test" ) +``` +will produce the output +```matlab +x = 2 +``` + +#### Tabular results +Queries returning multiple rows and columns are returned as a matlab `table` +```matlab +T = conn.query( "sql", "", "SELECT * FROM test ORDER BY id" ); +``` +It is possible to access the columns directly by doing +```matlab +T.id +T.name +``` +which in our example yields the output +```matlab +T.id = [1; 2] +T.name = {'Alice'; 'Bob'} +``` + +#### Empty results +Empty results +```matlab +T = conn.query( "sql", "~", "SELECT * FROM test WHERE id = 999" ); +``` +will be returned as empty MATLAB array: +```matlab +T = [] +``` + + +### SQL Batch Queries +Multiple non-SELECT statements can be executed using `queryBatch` +```matlab +conn.query( "sql", "", "DROP TABLE IF EXISTS test" ); +conn.query( "sql", "", "CREATE TABLE test (id INTEGER PRIMARY KEY, name VARCHAR)" ); + +queries = { + "INSERT INTO test VALUES (1,'Alice')" + "INSERT INTO test VALUES (2,'Bob')" +}; + +result = conn.queryBatch( "sql", "", queries ); +``` +where for `queries` containing `n` single queries, the result will be a `n x 1` vector with the i-th entry representing how many rows in the table the i-th query affected. Since each query inserts exactly one entry into one row, our example this yields the output +```matlab + RowsAffected + ____________ + + 1 + 1 +``` +Should single queries of a batch fail a rollback will be triggered (all or nothing principle). \ No newline at end of file From f4743d54c1707f71e695d4d624d61ca393b8d775 Mon Sep 17 00:00:00 2001 From: fygo97 Date: Tue, 23 Dec 2025 01:26:20 +0100 Subject: [PATCH 44/44] Finished the documentation for the user. --- documentation/user/README.md | 205 ++++++++++++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 2 deletions(-) diff --git a/documentation/user/README.md b/documentation/user/README.md index 8ee9598..bbf0f99 100644 --- a/documentation/user/README.md +++ b/documentation/user/README.md @@ -88,9 +88,12 @@ result = conn.query( 'language', 'namespace', queryString ); where `language` is an element of `{'sql', 'mongo', 'cypher'}`, `'namespace'` is the name of the namespace the query targets in the database and `queryString` is the string passed to the database. > **Note:** -> For Mongo queries the namespace argument is necessary for the creation and deletion of data structures in the backend. For SQL queries the namespace argument has no consequence and thus does not need to be set. In the following examples we will therefore use `""` as namespace argument. +> For Mongo queries the namespace argument is necessary for the creation and deletion of data structures in the backend. For SQL queries the namespace argument has no consequence and thus does not need to be set. In the following examples we will therefore use `""` as namespace argument for SQL. ### Executing SQL-queries +>**Note:** For relational query +results most primitive types used in our tests were cast to Matlab types. Edge cases were returned as Java objects in the table and will currently still have to be handled by the user. + Let us look at some practical examples ```matlab conn.query( "sql", "", "DROP TABLE IF EXISTS test" ); @@ -156,4 +159,202 @@ where for `queries` containing `n` single queries, the result will be a `n x 1` 1 1 ``` -Should single queries of a batch fail a rollback will be triggered (all or nothing principle). \ No newline at end of file +Should single queries of a batch fail a rollback will be triggered (all or nothing principle). +### Executing Mongo Queries + +The Polypheny MATLAB connector supports Mongo-style queries via Polypheny’s document model. Mongo queries return **raw JSON documents** as MATLAB strings. Automatic decoding is intentionally not performed. + +```matlab + result = conn.query( 'mongo', namespace, queryString ); +``` + +- `namespace` – Mongo adapter / namespace (**required**) +- `queryString` – Mongo-style query (**single statement only**) + +**Note:**: Unlike SQL, the `namespace` argument is required for Mongo queries. + +--- + +#### Return Type Semantics + +| Operation type | MATLAB return value | +|--------------------------|--------------------------| +| `find(...)` | string array (JSON docs) | +| `insertOne(...)` | acknowledgment JSON | +| `deleteMany(...)` | acknowledgment JSON | +| `countDocuments(...)` | acknowledgment JSON | +| empty result | `"[]"` (JSON string) | +| syntax / multi-statement | error | + + +#### Creating a Collection +```matlab +conn.query( "mongo", "demo", 'db.patients.drop()' ); +conn.query( "mongo", "demo", 'db.createCollection("patients")' ); +``` + +#### Inserting and Querying Documents +Documents can be inserted with +```matlab +conn.query( "mongo", "demo", 'db.patients.insertOne({"name":"Alice","age":34,"icu":true})' ); +``` +and queried using +```matlab +docs = conn.query( "mongo", "demo", 'db.patients.find({})' ); +disp(docs) +``` +will generate the output: +```text +"{""name"":""Alice"",""age"":34,""icu"":true}" +``` +and can be decoded using `jsondecode( docs ) ` which will return +```text + struct with fields: + + name: "Alice" + age: 34 + icu: true +``` +#### Decoding JSON in MATLAB using `jsondecode` +Since document queries can return a variety of results included nested documents, we will present some examples to illustrate the usage and possibilities of MATLAB's struct type. + +##### Example 1: Common Document Structure + +This example demonstrates working with **homogeneous Mongo documents** that all share the same schema. +After decoding, `jsondecode` returns a **struct array**, enabling vectorized access and logical indexing. + +```matlab +conn.query("mongo","demo", 'db.patients_mongo.drop()'); +conn.query("mongo","demo", 'db.createCollection("patients_mongo")'); + +conn.query("mongo","demo", ... + 'db.patients_mongo.insertOne({"name":"Alice Keller","age":34,"sex":"F", ... + "hospital":{"name":"Universitätsspital Basel","address":{"city":"Basel","zip":4051}}, ... + "infection_date":"2025-03-15","variant":"Omicron","vaccinated":true, ... + "icu":true,"recovered":true,"death":false})'); + +docs_all = conn.query("mongo","demo",'db.patients_mongo.find({})'); +decoded_all = jsondecode(docs_all); + ``` +will return +```text +1×1 struct array with fields: + + recovered + death + sex + infection_date + name + variant + icu + x_id + vaccinated + hospital + age +``` +The contents of the structure can be used through +```text +decoded_all.name + +ans = + 1×1 string array + "Alice Keller" +``` +and nested documents can equivalently accessed by +```text +decoded_all(1).hospital.name + +ans = + "Universitätsspital Basel" +``` + +#### Example 2: Nested Documents and Arrays +Insertions can consist of nested documents, e.g. the following code +```matlab +conn.query( "mongo", "demo",'db.patients.insertOne({"name":"Bob","meta":{"insurance":"LiveForeverInsurance","allergies":["nuts","penicillin"]}})' ); + +docs = conn.query( "mongo", "demo", 'db.patients.find({})' ); +decoded = jsondecode(docs); + +decoded(1).meta.insurance +decoded(1).meta.allergies +``` +will create the outputs: +```text +decoded = + + struct with fields: + + name: "Bob" + meta: [1×1 struct] + +ans = + "LiveForeverInsurance" + +ans = + 2×1 cell array + {'nuts'} + {'penicillin'} +``` + +#### Counting Documents +Counting documents using Mongo +```matlab +count_json = conn.query( "mongo", "demo", 'db.patients.countDocuments({})' ); +disp(count_json) +``` +will generate: + + {"count":1} +--- + +#### Empty Results +```matlab +docs = conn.query( "mongo", "demo", 'db.patients.find({})' ); +``` +Returned value: + + "[]" + +--- + +#### Batch Execution (Mongo) + + queries = { + 'db.patients.insertOne({"name":"Alice","age":25})' + 'db.patients.insertOne({"name":"Bob","age":30})' + }; + + conn.queryBatch( "mongo", "demo", queries ); + +Batch queries returning results: + + docs = conn.queryBatch( "mongo", "demo", { + 'db.patients.find({"name":"Alice"})' + 'db.patients.find({"name":"Bob"})' + }); + + decoded = jsondecode( docs ); + +--- + +### Error Handling + +The following conditions raise MATLAB errors: +- invalid JSON syntax +- multiple statements in a single query string +- unsupported operations + + badQuery = 'db.patients.insertOne({"foo":123)'; + conn.query( "mongo", "demo", badQuery ); + +--- + +### Design Rationale + +- Mongo results are returned as raw JSON +- Automatic decoding is avoided +- Users explicitly control conversion via jsondecode + +### Executing Cypher Queries +The Connector currently does not support Cypher queries yet.