diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTable.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTable.java
new file mode 100644
index 00000000..b0b607bf
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTable.java
@@ -0,0 +1,228 @@
+/*
+ * 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.core.model.tables;
+
+
+import java.time.Instant;
+import com.kingsrook.qqq.backend.core.model.data.QField;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
+
+
+/*******************************************************************************
+ ** QRecord Entity for QQQTable table
+ *******************************************************************************/
+public class QQQTable extends QRecordEntity
+{
+ public static final String TABLE_NAME = "qqqTable";
+
+ @QField(isEditable = false)
+ private Integer id;
+
+ @QField(isEditable = false)
+ private Instant createDate;
+
+ @QField(isEditable = false)
+ private Instant modifyDate;
+
+ @QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
+ private String name;
+
+ @QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
+ private String label;
+
+
+
+ /*******************************************************************************
+ ** Default constructor
+ *******************************************************************************/
+ public QQQTable()
+ {
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor that takes a QRecord
+ *******************************************************************************/
+ public QQQTable(QRecord record)
+ {
+ populateFromQRecord(record);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for id
+ *******************************************************************************/
+ public Integer getId()
+ {
+ return (this.id);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for id
+ *******************************************************************************/
+ public void setId(Integer id)
+ {
+ this.id = id;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for id
+ *******************************************************************************/
+ public QQQTable withId(Integer id)
+ {
+ this.id = id;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for createDate
+ *******************************************************************************/
+ public Instant getCreateDate()
+ {
+ return (this.createDate);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for createDate
+ *******************************************************************************/
+ public void setCreateDate(Instant createDate)
+ {
+ this.createDate = createDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for createDate
+ *******************************************************************************/
+ public QQQTable withCreateDate(Instant createDate)
+ {
+ this.createDate = createDate;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for modifyDate
+ *******************************************************************************/
+ public Instant getModifyDate()
+ {
+ return (this.modifyDate);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for modifyDate
+ *******************************************************************************/
+ public void setModifyDate(Instant modifyDate)
+ {
+ this.modifyDate = modifyDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for modifyDate
+ *******************************************************************************/
+ public QQQTable withModifyDate(Instant modifyDate)
+ {
+ this.modifyDate = modifyDate;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for name
+ *******************************************************************************/
+ public String getName()
+ {
+ return (this.name);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for name
+ *******************************************************************************/
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for name
+ *******************************************************************************/
+ public QQQTable withName(String name)
+ {
+ this.name = name;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for label
+ *******************************************************************************/
+ public String getLabel()
+ {
+ return (this.label);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for label
+ *******************************************************************************/
+ public void setLabel(String label)
+ {
+ this.label = label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for label
+ *******************************************************************************/
+ public QQQTable withLabel(String label)
+ {
+ this.label = label;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java
new file mode 100644
index 00000000..1f40e9ec
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java
@@ -0,0 +1,132 @@
+/*
+ * 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.core.model.tables;
+
+
+import java.util.function.Consumer;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
+import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class QQQTablesMetaDataProvider
+{
+ public static final String QQQ_TABLE_CACHE_TABLE_NAME = QQQTable.TABLE_NAME + "Cache";
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void defineAll(QInstance instance, String persistentBackendName, String cacheBackendName, Consumer backendDetailEnricher) throws QException
+ {
+ instance.addTable(defineQQQTable(persistentBackendName, backendDetailEnricher));
+ instance.addTable(defineQQQTableCache(cacheBackendName, backendDetailEnricher));
+ instance.addPossibleValueSource(defineQQQTablePossibleValueSource());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QTableMetaData defineQQQTable(String backendName, Consumer backendDetailEnricher) throws QException
+ {
+ QTableMetaData table = new QTableMetaData()
+ .withName(QQQTable.TABLE_NAME)
+ .withLabel("Table")
+ .withBackendName(backendName)
+ .withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE))
+ .withRecordLabelFormat("%s")
+ .withRecordLabelFields("label")
+ .withPrimaryKeyField("id")
+ .withUniqueKey(new UniqueKey("name"))
+ .withFieldsFromEntity(QQQTable.class)
+ .withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
+
+ if(backendDetailEnricher != null)
+ {
+ backendDetailEnricher.accept(table);
+ }
+
+ return (table);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QTableMetaData defineQQQTableCache(String backendName, Consumer backendDetailEnricher) throws QException
+ {
+ QTableMetaData table = new QTableMetaData()
+ .withName(QQQ_TABLE_CACHE_TABLE_NAME)
+ .withBackendName(backendName)
+ .withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE))
+ .withRecordLabelFormat("%s")
+ .withRecordLabelFields("label")
+ .withPrimaryKeyField("id")
+ .withUniqueKey(new UniqueKey("name"))
+ .withFieldsFromEntity(QQQTable.class)
+ .withCacheOf(new CacheOf()
+ .withSourceTable(QQQTable.TABLE_NAME)
+ .withUseCase(new CacheUseCase()
+ .withType(CacheUseCase.Type.UNIQUE_KEY_TO_UNIQUE_KEY)
+ .withCacheSourceMisses(false)
+ .withCacheUniqueKey(new UniqueKey("name"))
+ .withSourceUniqueKey(new UniqueKey("name"))
+ )
+ );
+
+ if(backendDetailEnricher != null)
+ {
+ backendDetailEnricher.accept(table);
+ }
+
+ return (table);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QPossibleValueSource defineQQQTablePossibleValueSource()
+ {
+ return (new QPossibleValueSource()
+ .withType(QPossibleValueSourceType.TABLE)
+ .withName(QQQTable.TABLE_NAME)
+ .withTableName(QQQTable.TABLE_NAME));
+ }
+
+}