diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSTableMetaDataBuilder.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSTableMetaDataBuilder.java
new file mode 100644
index 00000000..524712b1
--- /dev/null
+++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSTableMetaDataBuilder.java
@@ -0,0 +1,162 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.module.rdbms.model.metadata;
+
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
+
+
+/*******************************************************************************
+ ** note - this class is pretty mysql-specific
+ *******************************************************************************/
+public class RDBMSTableMetaDataBuilder
+{
+ private static final QLogger LOG = QLogger.getLogger(RDBMSTableMetaDataBuilder.class);
+
+ private static Map typeMap = new HashMap<>();
+
+ static
+ {
+ ////////////////////////////////////////////////
+ // these are types as returned by mysql //
+ // let null in here mean unsupported QQQ type //
+ ////////////////////////////////////////////////
+ typeMap.put("TEXT", QFieldType.TEXT);
+ typeMap.put("BINARY", QFieldType.BLOB);
+ typeMap.put("SET", null);
+ typeMap.put("VARBINARY", QFieldType.BLOB);
+ typeMap.put("MEDIUMBLOB", QFieldType.BLOB);
+ typeMap.put("NUMERIC", QFieldType.INTEGER);
+ typeMap.put("BIGINT UNSIGNED", QFieldType.LONG);
+ typeMap.put("MEDIUMINT UNSIGNED", QFieldType.INTEGER);
+ typeMap.put("SMALLINT UNSIGNED", QFieldType.INTEGER);
+ typeMap.put("TINYINT UNSIGNED", QFieldType.INTEGER);
+ typeMap.put("BIT", null);
+ typeMap.put("FLOAT", null);
+ typeMap.put("REAL", null);
+ typeMap.put("VARCHAR", QFieldType.STRING);
+ typeMap.put("BOOL", QFieldType.BOOLEAN);
+ typeMap.put("YEAR", null);
+ typeMap.put("TIME", QFieldType.TIME);
+ typeMap.put("TIMESTAMP", QFieldType.DATE_TIME);
+ }
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QTableMetaData buildTableMetaData(RDBMSBackendMetaData backendMetaData, String tableName) throws QException
+ {
+ try(Connection connection = new ConnectionManager().getConnection(backendMetaData))
+ {
+ List fieldMetaDataList = new ArrayList<>();
+ String primaryKey = null;
+
+ DatabaseMetaData databaseMetaData = connection.getMetaData();
+ Map dataTypeMap = new HashMap<>();
+ ResultSet typeInfoResultSet = databaseMetaData.getTypeInfo();
+ while(typeInfoResultSet.next())
+ {
+ String name = typeInfoResultSet.getString("TYPE_NAME");
+ Integer id = typeInfoResultSet.getInt("DATA_TYPE");
+ dataTypeMap.put(id, name);
+ }
+
+ String databaseName = backendMetaData.getDatabaseName(); // these work for mysql - unclear about other vendors.
+ String schemaName = null;
+ try(ResultSet tableResultSet = databaseMetaData.getTables(databaseName, schemaName, tableName, null))
+ {
+ if(!tableResultSet.next())
+ {
+ throw (new QException("Table: " + tableName + " was not found in backend: " + backendMetaData.getName()));
+ }
+ }
+
+ try(ResultSet columnsResultSet = databaseMetaData.getColumns(databaseName, schemaName, tableName, null))
+ {
+ while(columnsResultSet.next())
+ {
+ String columnName = columnsResultSet.getString("COLUMN_NAME");
+ String columnSize = columnsResultSet.getString("COLUMN_SIZE");
+ Integer dataTypeId = columnsResultSet.getInt("DATA_TYPE");
+ String isNullable = columnsResultSet.getString("IS_NULLABLE");
+ String isAutoIncrement = columnsResultSet.getString("IS_AUTOINCREMENT");
+
+ String dataTypeName = dataTypeMap.get(dataTypeId);
+ QFieldType type = typeMap.get(dataTypeName);
+ if(type == null)
+ {
+ LOG.info("Table " + tableName + " column " + columnName + " has an unampped type: " + dataTypeId + ". Field will not be added to QTableMetaData");
+ continue;
+ }
+
+ QFieldMetaData fieldMetaData = new QFieldMetaData(columnName, type)
+ // todo - what string? .withIsRequired(!isNullable)
+ .withLabel(columnName);
+
+ fieldMetaDataList.add(fieldMetaData);
+
+ if("YES".equals(isAutoIncrement))
+ {
+ primaryKey = columnName;
+ }
+ }
+ }
+
+ if(fieldMetaDataList.isEmpty())
+ {
+ throw (new QException("Could not find any usable fields in table: " + tableName));
+ }
+
+ if(primaryKey == null)
+ {
+ throw (new QException("Could not find primary key in table: " + tableName));
+ }
+
+ QTableMetaData tableMetaData = new QTableMetaData()
+ .withBackendName(backendMetaData.getName())
+ .withName(tableName)
+ .withLabel(tableName)
+ .withBackendDetails(new RDBMSTableBackendDetails().withTableName(tableName))
+ .withFields(fieldMetaDataList)
+ .withPrimaryKeyField(primaryKey);
+
+ return (tableMetaData);
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error automatically building table meta data for table: " + tableName, e));
+ }
+ }
+
+}