/* * QQQ - Low-code Application Framework for Engineers. * Copyright (C) 2021-2025. Kingsrook, LLC * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States * contact@kingsrook.com * https://github.com/Kingsrook/ * * 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. */ package com.kingsrook.qbits.dataanalysis; import java.util.ArrayList; import java.util.List; import com.kingsrook.qbits.dataanalysis.model.DataAnalysisTables; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey; /******************************************************************************* ** PostgreSQL DDL generated from the same tables and unique keys used by QQQ. *******************************************************************************/ public final class DataAnalysisSchema { /******************************************************************************* ** Utility class. *******************************************************************************/ private DataAnalysisSchema() { } /******************************************************************************* ** Idempotent table and unique index creation; hosts own migration execution. *******************************************************************************/ public static String postgresScript(DataAnalysisQBitConfig config) { List statements = new ArrayList<>(); for(QTableMetaData table : DataAnalysisTables.tables(config)) { List columns = new ArrayList<>(); for(QFieldMetaData field : table.getFields().values()) { columns.add(" " + field.getBackendName() + " " + ("id".equals(field.getName()) ? "INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY" : sqlType(field) + (Boolean.TRUE.equals(field.getIsRequired()) ? " NOT NULL" : ""))); } if(DataAnalysisTables.SAVED.equals(table.getName())) { /////////////////////////////////////////////////////////////////////////////// // a null read-owner projection means "shared": make the database refuse it // // on a private row, so a row written outside QQQ fails closed, not open // /////////////////////////////////////////////////////////////////////////////// columns.add(" CONSTRAINT ck_data_analysis_saved_read_owner CHECK (shared IS TRUE OR read_owner_user_id IS NOT NULL)"); } statements.add("CREATE TABLE IF NOT EXISTS " + table.getName() + " (\n" + String.join(",\n", columns) + "\n)"); if(table.getUniqueKeys() != null) { int index = 0; for(UniqueKey key : table.getUniqueKeys()) { index++; statements.add("CREATE UNIQUE INDEX IF NOT EXISTS uq_" + table.getName() + "_" + index + " ON " + table.getName() + " (" + String.join(", ", key.getFieldNames().stream().map(name -> table.getField(name).getBackendName()).toList()) + ")"); } } } return (String.join(";\n", statements) + ";\n"); } /******************************************************************************* ** PostgreSQL types for the table metadata. *******************************************************************************/ private static String sqlType(QFieldMetaData field) { return (switch(field.getType()) { case INTEGER -> "INTEGER"; case LONG -> "BIGINT"; case DECIMAL -> "NUMERIC"; case BOOLEAN -> "BOOLEAN"; case DATE_TIME -> "TIMESTAMP WITHOUT TIME ZONE"; case TEXT -> "TEXT"; default -> "VARCHAR(" + field.getMaxLength() + ")"; }); } }