gh-yzou commented on code in PR #1287:
URL: https://github.com/apache/polaris/pull/1287#discussion_r2052972975


##########
extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/DatasourceOperations.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.extension.persistence.relational.jdbc;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import jakarta.annotation.Nonnull;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import javax.sql.DataSource;
+import javax.swing.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DatasourceOperations {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(DatasourceOperations.class);
+
+  private static final String ALREADY_EXISTS_STATE_POSTGRES = "42P07";
+  private static final String CONSTRAINT_VIOLATION_SQL_CODE = "23505";
+
+  private final DataSource datasource;
+
+  public DatasourceOperations(DataSource datasource) {
+    this.datasource = datasource;
+  }
+
+  public void executeScript(String scriptFilePath) throws SQLException {

Review Comment:
   can you add some comment for this function to explain what is this function 
doing here? 



##########
extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/DatasourceOperations.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.extension.persistence.relational.jdbc;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import jakarta.annotation.Nonnull;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import javax.sql.DataSource;
+import javax.swing.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DatasourceOperations {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(DatasourceOperations.class);
+
+  private static final String ALREADY_EXISTS_STATE_POSTGRES = "42P07";
+  private static final String CONSTRAINT_VIOLATION_SQL_CODE = "23505";
+
+  private final DataSource datasource;
+
+  public DatasourceOperations(DataSource datasource) {
+    this.datasource = datasource;
+  }
+
+  public void executeScript(String scriptFilePath) throws SQLException {
+    ClassLoader classLoader = DatasourceOperations.class.getClassLoader();
+    try (Connection connection = borrowConnection();
+        Statement statement = connection.createStatement()) {
+      boolean autoCommit = connection.getAutoCommit();
+      connection.setAutoCommit(true);
+      try {
+        BufferedReader reader =
+            new BufferedReader(
+                new InputStreamReader(
+                    
Objects.requireNonNull(classLoader.getResourceAsStream(scriptFilePath)),
+                    UTF_8));
+        StringBuilder sqlBuffer = new StringBuilder();
+        String line;
+        while ((line = reader.readLine()) != null) {
+          line = line.trim();
+          if (!line.isEmpty() && !line.startsWith("--")) { // Ignore empty 
lines and comments
+            sqlBuffer.append(line).append("\n");
+            if (line.endsWith(";")) { // Execute statement when semicolon is 
found
+              String sql = sqlBuffer.toString().trim();
+              try {
+                int rowsUpdated = statement.executeUpdate(sql);
+                LOGGER.debug("Query {} executed {} rows affected", sql, 
rowsUpdated);
+              } catch (SQLException e) {
+                LOGGER.error("Error executing query {}", sql, e);
+                // re:throw this as unhandled exception
+                throw new RuntimeException(e);
+              }
+              sqlBuffer.setLength(0); // Clear the buffer for the next 
statement
+            }
+          }
+        }
+      } finally {
+        connection.setAutoCommit(autoCommit);
+      }
+    } catch (IOException e) {
+      LOGGER.error("Error reading the script file", e);
+      throw new RuntimeException(e);
+    } catch (SQLException e) {
+      LOGGER.error("Error executing the script file", e);
+      throw e;
+    }
+  }
+
+  public <T, R> List<R> executeSelect(
+      @Nonnull String query,

Review Comment:
   Please add some java doc for this function, some parameter like transformer 
is not very straightforward for understanding, it would be great if we could 
have some example in the comment also.



##########
extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/QueryGenerator.java:
##########
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.extension.persistence.relational.jdbc;
+
+import com.google.common.annotations.VisibleForTesting;
+import jakarta.annotation.Nonnull;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.polaris.core.entity.PolarisEntityCore;
+import org.apache.polaris.core.entity.PolarisEntityId;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelEntity;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelGrantRecord;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelPrincipalAuthenticationData;
+
+public class QueryGenerator {

Review Comment:
   can we add comments for the functions in this class in general? for example, 
for the generateSelectQuery 
   ```
   when entityClass is ModelEntity.class, and whereClause is 
{"name":"test-entity", "entity_version": 1}, the query generated will be 
"select xxx from POLARIS_SCHEMA.ENTITIES WHERE entity_version = 1 AND name = 
'test-entity'";
   ```



##########
extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/ResultSetToObjectConverter.java:
##########
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.extension.persistence.relational.jdbc;
+
+import jakarta.annotation.Nonnull;
+import java.lang.reflect.Field;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class ResultSetToObjectConverter {
+
+  public static <T, R> List<R> collect(
+      @Nonnull ResultSet resultSet,
+      @Nonnull Class<T> targetClass,
+      @Nonnull Function<T, R> transformer,
+      Predicate<R> entityFilter,
+      int limit)
+      throws ReflectiveOperationException, SQLException {
+    List<R> resultList = new ArrayList<>();
+    ResultSetMetaData metaData = resultSet.getMetaData();
+    int columnCount = metaData.getColumnCount();
+    String[] columnNames = new String[columnCount + 1]; // 1-based indexing
+
+    for (int i = 1; i <= columnCount; i++) {
+      columnNames[i] =
+          metaData
+              .getColumnLabel(i)
+              .toLowerCase(Locale.ROOT); // or getColumnName(), lowercase to 
match field names
+    }
+
+    while (resultSet.next() && resultList.size() < limit) {
+      T object = targetClass.getDeclaredConstructor().newInstance(); // Create 
a new instance
+      for (int i = 1; i <= columnCount; i++) {
+        String columnName = columnNames[i];
+        Object value;
+        // TODO: This handling doesn't works for H2, works fine with Postgres.
+        //        if (columnName.contains("properties")) {
+        //          value = resultSet.getString(i);
+        //        } else {
+        //          value = resultSet.getObject(i);
+        //        }
+        value = resultSet.getObject(i);
+
+        try {
+          Field field = targetClass.getDeclaredField(columnName);
+          field.setAccessible(true); // Allow access to private fields
+          field.set(object, value);
+        } catch (NoSuchFieldException e) {
+          // Handle case where column name doesn't match field name (e.g., 
snake_case vs. camelCase)
+          // You could implement more sophisticated matching logic here, or 
use annotations.
+          try {
+            Field field = 
targetClass.getDeclaredField(convertSnakeCaseToCamelCase(columnName));
+            field.setAccessible(true);
+            field.set(object, value);
+          } catch (NoSuchFieldException e2) {
+            // if still not found, just skip it.
+          }
+        }
+      }
+      R entity = transformer.apply(object);
+      if (entityFilter == null || entityFilter.test(entity)) {
+        resultList.add(entity);
+      }
+    }
+    return resultList;
+  }
+
+  private static String convertSnakeCaseToCamelCase(String snakeCase) {

Review Comment:
   can you add comment for this function about what it is doing, and why we 
need this function?



##########
extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/ResultSetToObjectConverter.java:
##########
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.extension.persistence.relational.jdbc;
+
+import jakarta.annotation.Nonnull;
+import java.lang.reflect.Field;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class ResultSetToObjectConverter {
+
+  public static <T, R> List<R> collect(
+      @Nonnull ResultSet resultSet,
+      @Nonnull Class<T> targetClass,
+      @Nonnull Function<T, R> transformer,
+      Predicate<R> entityFilter,
+      int limit)
+      throws ReflectiveOperationException, SQLException {
+    List<R> resultList = new ArrayList<>();
+    ResultSetMetaData metaData = resultSet.getMetaData();
+    int columnCount = metaData.getColumnCount();
+    String[] columnNames = new String[columnCount + 1]; // 1-based indexing
+
+    for (int i = 1; i <= columnCount; i++) {
+      columnNames[i] =
+          metaData
+              .getColumnLabel(i)
+              .toLowerCase(Locale.ROOT); // or getColumnName(), lowercase to 
match field names
+    }
+
+    while (resultSet.next() && resultList.size() < limit) {
+      T object = targetClass.getDeclaredConstructor().newInstance(); // Create 
a new instance
+      for (int i = 1; i <= columnCount; i++) {
+        String columnName = columnNames[i];
+        Object value;
+        // TODO: This handling doesn't works for H2, works fine with Postgres.
+        //        if (columnName.contains("properties")) {
+        //          value = resultSet.getString(i);
+        //        } else {
+        //          value = resultSet.getObject(i);
+        //        }
+        value = resultSet.getObject(i);
+
+        try {
+          Field field = targetClass.getDeclaredField(columnName);
+          field.setAccessible(true); // Allow access to private fields
+          field.set(object, value);
+        } catch (NoSuchFieldException e) {
+          // Handle case where column name doesn't match field name (e.g., 
snake_case vs. camelCase)
+          // You could implement more sophisticated matching logic here, or 
use annotations.
+          try {
+            Field field = 
targetClass.getDeclaredField(convertSnakeCaseToCamelCase(columnName));

Review Comment:
   + 1. I think we should always first do a convert to filed Name first, an 
explicit mapping could help making things more clear and get rid of the 
reflection here as well.



##########
extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/QueryGenerator.java:
##########
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.extension.persistence.relational.jdbc;
+
+import com.google.common.annotations.VisibleForTesting;
+import jakarta.annotation.Nonnull;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.polaris.core.entity.PolarisEntityCore;
+import org.apache.polaris.core.entity.PolarisEntityId;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelEntity;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelGrantRecord;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelPrincipalAuthenticationData;
+
+public class QueryGenerator {
+
+  private static final Pattern CAMEL_CASE_PATTERN =
+      Pattern.compile("(?<=[a-z0-9])[A-Z]|(?<=[A-Z])[A-Z](?=[a-z])");
+
+  public static String generateSelectQuery(
+      @Nonnull Class<?> entityClass, @Nonnull Map<String, Object> whereClause) 
{
+    return generateSelectQuery(entityClass, generateWhereClause(whereClause));
+  }
+
+  public static String generateDeleteQueryForEntityGrantRecords(
+      @Nonnull PolarisEntityCore entity, @Nonnull String realmId) {
+    String granteeCondition =
+        String.format(
+            "grantee_id = %s AND grantee_catalog_id = %s", entity.getId(), 
entity.getCatalogId());
+    String securableCondition =
+        String.format(
+            "securable_id = %s AND securable_catalog_id = %s",
+            entity.getId(), entity.getCatalogId());
+
+    String whereClause =
+        " WHERE ("
+            + granteeCondition
+            + " OR "
+            + securableCondition
+            + ") AND realm_id = '"
+            + realmId
+            + "'";
+    return QueryGenerator.generateDeleteQuery(ModelGrantRecord.class, 
whereClause);
+  }
+
+  public static String generateSelectQueryWithEntityIds(
+      @Nonnull String realmId, @Nonnull List<PolarisEntityId> entityIds) {
+    if (entityIds.isEmpty()) {
+      throw new IllegalArgumentException("Empty entity ids");
+    }
+    StringBuilder condition = new StringBuilder("(catalog_id, id) IN (");
+    for (PolarisEntityId entityId : entityIds) {
+      String in = "(" + entityId.getCatalogId() + ", " + entityId.getId() + 
")";
+      condition.append(in);
+      condition.append(",");
+    }
+    // extra , removed
+    condition.deleteCharAt(condition.length() - 1);
+    condition.append(")");
+    condition.append(" AND realm_id = '").append(realmId).append("'");
+
+    return generateSelectQuery(ModelEntity.class, " WHERE " + condition);
+  }
+
+  public static String generateInsertQuery(@Nonnull Object object, @Nonnull 
String realmId) {
+    String tableName = getTableName(object.getClass());
+
+    Class<?> objectClass = object.getClass();
+    Field[] fields = objectClass.getDeclaredFields();
+    List<String> columnNames = new ArrayList<>();
+    List<String> values = new ArrayList<>();
+    columnNames.add("realm_id");
+    values.add("'" + realmId + "'");
+
+    for (Field field : fields) {
+      field.setAccessible(true); // Allow access to private fields
+      try {
+        Object value = field.get(object);
+        if (value != null) { // Only include non-null fields
+          columnNames.add(camelToSnake(field.getName()));
+          values.add("'" + value + "'");
+        }
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    if (columnNames.isEmpty()) {
+      throw new RuntimeException("No column names found");
+    }
+
+    String columns = String.join(", ", columnNames);
+    String valuesString = String.join(", ", values);
+
+    return "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + 
valuesString + ")";
+  }
+
+  public static String generateUpdateQuery(
+      @Nonnull Object object, @Nonnull Map<String, Object> whereClause) {
+    Class<?> objectClass = object.getClass();
+    String tableName = getTableName(objectClass);
+    List<String> setClauses = new ArrayList<>();
+    Field[] fields = objectClass.getDeclaredFields();
+    List<String> columnNames = new ArrayList<>();
+    List<Object> values = new ArrayList<>();
+
+    for (Field field : fields) {
+      field.setAccessible(true); // Allow access to private fields
+      try {
+        Object value = field.get(object);
+        if (value != null) { // Only include non-null fields
+          columnNames.add(camelToSnake(field.getName()));
+          values.add("'" + value + "'");
+        }
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    for (int i = 0; i < columnNames.size(); i++) {
+      setClauses.add(columnNames.get(i) + " = " + values.get(i)); // 
Placeholders
+    }
+
+    String setClausesString = String.join(", ", setClauses);
+
+    return "UPDATE " + tableName + " SET " + setClausesString + 
generateWhereClause(whereClause);
+  }
+
+  public static String generateDeleteQuery(
+      @Nonnull Class<?> entityClass, @Nonnull Map<String, Object> whereClause) 
{
+    String tableName = getTableName(entityClass);
+    return "DELETE FROM " + tableName + (generateWhereClause(whereClause));
+  }
+
+  public static String generateDeleteQuery(
+      @Nonnull Class<?> entityClass, @Nonnull String whereClause) {
+    return "DELETE FROM " + getTableName(entityClass) + whereClause;
+  }
+
+  public static String generateDeleteAll(@Nonnull Class<?> entityClass, 
@Nonnull String realmId) {
+    String tableName = getTableName(entityClass);
+    return "DELETE FROM " + tableName + " WHERE 1 = 1 AND realm_id = '" + 
realmId + "'";
+  }
+
+  public static String generateDeleteQuery(
+      @Nonnull Object obj, @Nonnull Class<?> entityClass, @Nonnull String 
realmId) {
+    String tableName = getTableName(entityClass);
+    List<String> whereConditions = new ArrayList<>();
+
+    Class<?> objectClass = obj.getClass();

Review Comment:
   is the obj.getClass the same as entityClass? why not just get both from the 
obj? and we can rename obj -> entity to be more clear



##########
extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/QueryGenerator.java:
##########
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.extension.persistence.relational.jdbc;
+
+import com.google.common.annotations.VisibleForTesting;
+import jakarta.annotation.Nonnull;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.polaris.core.entity.PolarisEntityCore;
+import org.apache.polaris.core.entity.PolarisEntityId;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelEntity;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelGrantRecord;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelPrincipalAuthenticationData;
+
+public class QueryGenerator {

Review Comment:
   and + 1 on @flyrain 's suggestion to move to use an existing library to deal 
with code generation instead of writing all those from scratch, maybe we can do 
that as followups



##########
extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/JdbcCrudQueryGenerator.java:
##########
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.extension.persistence.relational.jdbc;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.polaris.core.entity.PolarisEntityCore;
+import org.apache.polaris.core.entity.PolarisEntityId;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelEntity;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelGrantRecord;
+import 
org.apache.polaris.extension.persistence.relational.jdbc.models.ModelPrincipalAuthenticationData;
+
+public class JdbcCrudQueryGenerator {
+
+  private static final Pattern CAMEL_CASE_PATTERN =
+      Pattern.compile("(?<=[a-z0-9])[A-Z]|(?<=[A-Z])[A-Z](?=[a-z])");
+
+  public static String generateSelectQuery(
+      Class<?> entityClass, String filter, Integer limit, Integer offset, 
String orderBy) {
+    String tableName = getTableName(entityClass);
+    List<String> fields = new ArrayList<>();
+
+    for (Field field : entityClass.getDeclaredFields()) {
+      fields.add(camelToSnake(field.getName()));
+    }
+
+    String columns = String.join(", ", fields);
+    StringBuilder query =
+        new StringBuilder("SELECT ").append(columns).append(" FROM 
").append(tableName);
+    if (filter != null && !filter.isEmpty()) {
+      query.append(" WHERE ").append(String.join(" AND ", filter));
+    }
+
+    return query.toString();
+  }
+
+  public static String generateSelectQuery(
+      Class<?> entityClass,
+      Map<String, Object> whereClause,
+      Integer limit,
+      Integer offset,
+      String orderBy) {
+    String tableName = getTableName(entityClass);
+    List<String> fields = new ArrayList<>();
+
+    for (Field field : entityClass.getDeclaredFields()) {
+      fields.add(camelToSnake(field.getName()));
+    }
+
+    String columns = String.join(", ", fields);
+    StringBuilder query =
+        new StringBuilder("SELECT ").append(columns).append(" FROM 
").append(tableName);
+
+    if (whereClause != null && !whereClause.isEmpty()) {
+      query.append(generateWhereClause(whereClause));
+    }
+
+    if (orderBy != null && !orderBy.isEmpty()) {
+      query.append(" ORDER BY ").append(orderBy);
+    }
+
+    if (limit != null) {
+      query.append(" LIMIT ").append(limit);
+    }
+
+    if (offset != null && limit != null) { // Offset only makes sense with 
limit.
+      query.append(" OFFSET ").append(offset);
+    }
+
+    return query.toString();
+  }
+
+  public static String generateDeleteQueryForEntityGrantRecords(
+      PolarisEntityCore entity, String realmId) {
+    // generate where clause
+    StringBuilder granteeCondition = new StringBuilder("(grantee_id, 
grantee_catalog_id) IN (");
+    granteeCondition
+        .append("(")
+        .append(entity.getId())
+        .append(", ")
+        .append(entity.getCatalogId())
+        .append(")");
+    granteeCondition.append(",");
+    // extra , removed
+    granteeCondition.deleteCharAt(granteeCondition.length() - 1);
+    granteeCondition.append(")");
+
+    StringBuilder securableCondition =
+        new StringBuilder("(securable_catalog_id, securable_id) IN (");
+
+    String in = "(" + entity.getCatalogId() + ", " + entity.getId() + ")";
+    securableCondition.append(in);
+    securableCondition.append(",");
+
+    // extra , removed
+    securableCondition.deleteCharAt(securableCondition.length() - 1);
+    securableCondition.append(")");
+
+    String whereClause =
+        " WHERE ("
+            + granteeCondition
+            + " OR "
+            + securableCondition
+            + ") AND realm_id = '"
+            + realmId
+            + "'";
+    return JdbcCrudQueryGenerator.generateDeleteQuery(ModelGrantRecord.class, 
whereClause);
+  }
+
+  public static String generateSelectQueryForMultipleEntities(
+      String realmId, List<PolarisEntityId> entityIds) {
+    StringBuilder condition = new StringBuilder("(catalog_id, id) IN (");
+    for (PolarisEntityId entityId : entityIds) {
+      String in = "(" + entityId.getCatalogId() + ", " + entityId.getId() + 
")";
+      condition.append(in);
+      condition.append(",");
+    }
+    // extra , removed
+    condition.deleteCharAt(condition.length() - 1);
+    condition.append(")");
+    condition.append(" AND realm_id = '").append(realmId).append("'");
+    return JdbcCrudQueryGenerator.generateSelectQuery(
+        ModelEntity.class, entityIds.isEmpty() ? "" : 
String.valueOf(condition), null, null, null);
+  }
+
+  public static String generateInsertQuery(Object object, String realmId) {
+    if (object == null) {
+      return null;
+    }
+
+    String tableName = getTableName(object.getClass());
+
+    Class<?> objectClass = object.getClass();
+    Field[] fields = objectClass.getDeclaredFields();
+    List<String> columnNames = new ArrayList<>();
+    List<String> values = new ArrayList<>();
+    columnNames.add("realm_id");
+    values.add("'" + realmId + "'");
+
+    for (Field field : fields) {
+      field.setAccessible(true); // Allow access to private fields
+      try {
+        Object value = field.get(object);

Review Comment:
   In general, I would avoid using reflection unless there is no other good 
ways, since there are many draw backs with reflection, includes performance, 
readability, maintainability and security etc. Have a toMap methods for each 
model sounds reasonable to me, similar as we have a toEntity Methods for each 
model also.  I see @dimas-b also had a similar suggestion about, which i think 
it is a nice suggestion that could help improve the code readability and  
performance.



##########
extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/DatasourceOperations.java:
##########
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.polaris.extension.persistence.relational.jdbc;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import jakarta.annotation.Nonnull;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import javax.sql.DataSource;
+import javax.swing.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DatasourceOperations {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(DatasourceOperations.class);
+
+  private static final String ALREADY_EXISTS_STATE_POSTGRES = "42P07";

Review Comment:
   sorry, i meant add a comment here about where do we get the code, so at 
least when people look at the code, they know how too find the ERROR CODE. I 
don't mean import postgres dependency here



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@polaris.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to