Checking record security locks that are more than 1 join away.

This commit is contained in:
2023-03-29 09:55:05 -05:00
parent e62d2332ac
commit ef6ccc61c3
18 changed files with 634 additions and 151 deletions

View File

@ -47,6 +47,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.Aggregate;
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.GroupBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByAggregate;
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.ImplicitQueryJoinForSecurityLock;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
@ -218,7 +219,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
String baseTableName = Objects.requireNonNullElse(joinsContext.resolveTableNameOrAliasToTableName(queryJoin.getBaseTableOrAlias()), tableName);
QJoinMetaData joinMetaData = Objects.requireNonNullElseGet(queryJoin.getJoinMetaData(), () ->
{
QJoinMetaData found = findJoinMetaData(instance, joinsContext, baseTableName, queryJoin.getJoinTable());
QJoinMetaData found = joinsContext.findJoinMetaData(instance, baseTableName, queryJoin.getJoinTable());
if(found == null)
{
throw (new RuntimeException("Could not find a join between tables [" + baseTableName + "][" + queryJoin.getJoinTable() + "]"));
@ -263,69 +264,6 @@ public abstract class AbstractRDBMSAction implements QActionInterface
/*******************************************************************************
**
*******************************************************************************/
private QJoinMetaData findJoinMetaData(QInstance instance, JoinsContext joinsContext, String baseTableName, String joinTableName)
{
List<QJoinMetaData> matches = new ArrayList<>();
if(baseTableName != null)
{
///////////////////////////////////////////////////////////////////////////
// if query specified a left-table, look for a join between left & right //
///////////////////////////////////////////////////////////////////////////
for(QJoinMetaData join : instance.getJoins().values())
{
if(join.getLeftTable().equals(baseTableName) && join.getRightTable().equals(joinTableName))
{
matches.add(join);
}
//////////////////////////////
// look in both directions! //
//////////////////////////////
if(join.getRightTable().equals(baseTableName) && join.getLeftTable().equals(joinTableName))
{
matches.add(join.flip());
}
}
}
else
{
/////////////////////////////////////////////////////////////////////////////////////
// if query didn't specify a left-table, then look for any join to the right table //
/////////////////////////////////////////////////////////////////////////////////////
for(QJoinMetaData join : instance.getJoins().values())
{
if(join.getRightTable().equals(joinTableName) && joinsContext.hasTable(join.getLeftTable()))
{
matches.add(join);
}
//////////////////////////////
// look in both directions! //
//////////////////////////////
if(join.getLeftTable().equals(joinTableName) && joinsContext.hasTable(join.getRightTable()))
{
matches.add(join.flip());
}
}
}
if(matches.size() == 1)
{
return (matches.get(0));
}
else if(matches.size() > 1)
{
throw (new RuntimeException("More than 1 join was found between [" + baseTableName + "] and [" + joinTableName + "]. Specify which one in your QueryJoin."));
}
return (null);
}
/*******************************************************************************
** method that sub-classes should call to make a full WHERE clause, including
** security clauses.
@ -403,6 +341,16 @@ public abstract class AbstractRDBMSAction implements QActionInterface
for(QueryJoin queryJoin : CollectionUtils.nonNullList(joinsContext.getQueryJoins()))
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// for user-added joins, we want to add their security-locks to the query //
// but if a join was implicitly added because it's needed to find a security lock on table being queried, //
// don't add additional layers of locks for each join table. that's the idea here at least. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(queryJoin instanceof ImplicitQueryJoinForSecurityLock)
{
continue;
}
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(joinTable.getRecordSecurityLocks()))
{
@ -436,43 +384,10 @@ public abstract class AbstractRDBMSAction implements QActionInterface
}
}
String fieldName = tableNameOrAlias + "." + recordSecurityLock.getFieldName();
String fieldNameWithoutTablePrefix = recordSecurityLock.getFieldName().replaceFirst(".*\\.", "");
String fieldNameTablePrefix = recordSecurityLock.getFieldName().replaceFirst("\\..*", "");
String fieldName = tableNameOrAlias + "." + recordSecurityLock.getFieldName();
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()))
{
for(String joinName : recordSecurityLock.getJoinNameChain())
{
QJoinMetaData joinMetaData = instance.getJoin(joinName);
/*
for(QueryJoin queryJoin : joinsContext.getQueryJoins())
{
if(queryJoin.getJoinMetaData().getName().equals(joinName))
{
joinMetaData = queryJoin.getJoinMetaData();
break;
}
}
*/
if(joinMetaData == null)
{
throw (new RuntimeException("Could not find joinMetaData for recordSecurityLock with joinChain member [" + joinName + "]"));
}
if(fieldNameTablePrefix.equals(joinMetaData.getLeftTable()))
{
table = instance.getTable(joinMetaData.getLeftTable());
}
else
{
table = instance.getTable(joinMetaData.getRightTable());
}
tableNameOrAlias = table.getName();
fieldName = tableNameOrAlias + "." + fieldNameWithoutTablePrefix;
}
fieldName = recordSecurityLock.getFieldName();
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -481,7 +396,19 @@ public abstract class AbstractRDBMSAction implements QActionInterface
QQueryFilter lockFilter = new QQueryFilter();
List<QFilterCriteria> lockCriteria = new ArrayList<>();
lockFilter.setCriteria(lockCriteria);
List<Serializable> securityKeyValues = session.getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), table.getField(fieldNameWithoutTablePrefix).getType());
QFieldType type = QFieldType.INTEGER;
try
{
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(fieldName);
type = fieldAndTableNameOrAlias.field().getType();
}
catch(Exception e)
{
LOG.debug("Error getting field type... Trying Integer", e);
}
List<Serializable> securityKeyValues = session.getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), type);
if(CollectionUtils.nullSafeIsEmpty(securityKeyValues))
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -82,7 +82,7 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
.toList();
String columns = insertableFields.stream()
.map(this::getColumnName)
.map(f -> "`" + getColumnName(f) + "`")
.collect(Collectors.joining(", "));
String questionMarks = insertableFields.stream()
.map(x -> "?")

View File

@ -25,6 +25,11 @@ package com.kingsrook.qqq.backend.module.rdbms;
import java.io.InputStream;
import java.sql.Connection;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
@ -38,6 +43,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSActionTest;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
@ -61,6 +67,7 @@ public class TestUtils
public static final String TABLE_NAME_ORDER = "order";
public static final String TABLE_NAME_ITEM = "item";
public static final String TABLE_NAME_ORDER_LINE = "orderLine";
public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic";
public static final String TABLE_NAME_WAREHOUSE = "warehouse";
public static final String TABLE_NAME_WAREHOUSE_STORE_INT = "warehouseStoreInt";
@ -231,6 +238,7 @@ public class TestUtils
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER, "order")
.withRecordSecurityLock(new RecordSecurityLock().withSecurityKeyType(TABLE_NAME_STORE).withFieldName("storeId"))
.withAssociation(new Association().withName("orderLine").withAssociatedTableName(TABLE_NAME_ORDER_LINE).withJoinName("orderJoinOrderLine"))
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id").withPossibleValueSourceName(TABLE_NAME_STORE))
.withField(new QFieldMetaData("billToPersonId", QFieldType.INTEGER).withBackendName("bill_to_person_id").withPossibleValueSourceName(TABLE_NAME_PERSON))
.withField(new QFieldMetaData("shipToPersonId", QFieldType.INTEGER).withBackendName("ship_to_person_id").withPossibleValueSourceName(TABLE_NAME_PERSON))
@ -243,13 +251,28 @@ public class TestUtils
);
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER_LINE, "order_line")
.withRecordSecurityLock(new RecordSecurityLock().withSecurityKeyType(TABLE_NAME_STORE).withFieldName("storeId"))
.withRecordSecurityLock(new RecordSecurityLock()
.withSecurityKeyType(TABLE_NAME_STORE)
.withFieldName("order.storeId")
.withJoinNameChain(List.of("orderJoinOrderLine")))
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_LINE_ITEM_EXTRINSIC).withJoinName("orderLineJoinLineItemExtrinsic"))
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER).withBackendName("order_id"))
.withField(new QFieldMetaData("sku", QFieldType.STRING))
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id").withPossibleValueSourceName(TABLE_NAME_STORE))
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER))
);
qInstance.addTable(defineBaseTable(TABLE_NAME_LINE_ITEM_EXTRINSIC, "line_item_extrinsic")
.withRecordSecurityLock(new RecordSecurityLock()
.withSecurityKeyType(TABLE_NAME_STORE)
.withFieldName("order.storeId")
.withJoinNameChain(List.of("orderJoinOrderLine", "orderLineJoinLineItemExtrinsic")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("orderLineId", QFieldType.INTEGER).withBackendName("order_line_id"))
.withField(new QFieldMetaData("key", QFieldType.STRING))
.withField(new QFieldMetaData("value", QFieldType.STRING))
);
qInstance.addTable(defineBaseTable(TABLE_NAME_WAREHOUSE_STORE_INT, "warehouse_store_int")
.withField(new QFieldMetaData("warehouseId", QFieldType.INTEGER).withBackendName("warehouse_id"))
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id"))
@ -321,6 +344,14 @@ public class TestUtils
.withJoinOn(new JoinOn("storeId", "storeId"))
);
qInstance.addJoin(new QJoinMetaData()
.withName("orderLineJoinLineItemExtrinsic")
.withLeftTable(TABLE_NAME_ORDER_LINE)
.withRightTable(TABLE_NAME_LINE_ITEM_EXTRINSIC)
.withType(JoinType.ONE_TO_MANY)
.withJoinOn(new JoinOn("id", "orderLineId"))
);
qInstance.addPossibleValueSource(new QPossibleValueSource()
.withName("store")
.withType(QPossibleValueSourceType.TABLE)
@ -349,4 +380,16 @@ public class TestUtils
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
}
/*******************************************************************************
**
*******************************************************************************/
public static List<QRecord> queryTable(String tableName) throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
return (queryOutput.getRecords());
}
}

View File

@ -329,8 +329,7 @@ public class RDBMSAggregateActionTest extends RDBMSActionTest
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 1));
aggregateOutput = new RDBMSAggregateAction().execute(aggregateInput);
aggregateResult = aggregateOutput.getResults().get(0);
// note - this would be 33, except for that one order line that has a contradictory store id...
Assertions.assertEquals(32, aggregateResult.getAggregateValue(sumOfQuantity));
Assertions.assertEquals(33, aggregateResult.getAggregateValue(sumOfQuantity));
}

View File

@ -24,6 +24,9 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
@ -34,6 +37,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
@ -140,6 +144,50 @@ public class RDBMSInsertActionTest extends RDBMSActionTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testInsertAssociations() throws QException
{
QContext.getQSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 1);
int originalNoOfOrderLineExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size();
int originalNoOfOrderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_LINE).size();
int originalNoOfOrders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER).size();
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertInput.setRecords(List.of(
new QRecord().withValue("storeId", 1).withValue("billToPersonId", 100).withValue("shipToPersonId", 200)
.withAssociatedRecord("orderLine", new QRecord().withValue("sku", "BASIC1").withValue("quantity", 1)
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "LINE-EXT-1.1").withValue("value", "LINE-VAL-1")))
.withAssociatedRecord("orderLine", new QRecord().withValue("sku", "BASIC2").withValue("quantity", 2)
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "LINE-EXT-2.1").withValue("value", "LINE-VAL-2"))
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "LINE-EXT-2.2").withValue("value", "LINE-VAL-3")))
));
new InsertAction().execute(insertInput);
List<QRecord> orders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER);
assertEquals(originalNoOfOrders + 1, orders.size());
assertTrue(orders.stream().anyMatch(r -> Objects.equals(r.getValue("billToPersonId"), 100) && Objects.equals(r.getValue("shipToPersonId"), 200)));
List<QRecord> orderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_LINE);
assertEquals(originalNoOfOrderLines + 2, orderLines.size());
assertTrue(orderLines.stream().anyMatch(r -> Objects.equals(r.getValue("sku"), "BASIC1") && Objects.equals(r.getValue("quantity"), 1)));
assertTrue(orderLines.stream().anyMatch(r -> Objects.equals(r.getValue("sku"), "BASIC2") && Objects.equals(r.getValue("quantity"), 2)));
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
assertEquals(originalNoOfOrderLineExtrinsics + 3, lineItemExtrinsics.size());
assertTrue(lineItemExtrinsics.stream().anyMatch(r -> Objects.equals(r.getValue("key"), "LINE-EXT-1.1") && Objects.equals(r.getValue("value"), "LINE-VAL-1")));
assertTrue(lineItemExtrinsics.stream().anyMatch(r -> Objects.equals(r.getValue("key"), "LINE-EXT-2.1") && Objects.equals(r.getValue("value"), "LINE-VAL-2")));
assertTrue(lineItemExtrinsics.stream().anyMatch(r -> Objects.equals(r.getValue("key"), "LINE-EXT-2.2") && Objects.equals(r.getValue("value"), "LINE-VAL-3")));
}
private void assertAnInsertedPersonRecord(String firstName, String lastName, Integer id) throws Exception
{
runTestSql("SELECT * FROM person WHERE last_name = '" + lastName + "'", (rs -> {

View File

@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@ -162,6 +163,7 @@ public class GenerateReportActionRDBMSTest extends RDBMSActionTest
.withDataSource(new QReportDataSource()
.withSourceTable(TestUtils.TABLE_NAME_ORDER_LINE)
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ITEM).withAlias("i").withSelect(true))
.withQueryFilter(new QQueryFilter().withOrderBy(new QFilterOrderBy("id")))
)
.withView(new QReportView()
.withType(ReportType.TABLE)
@ -179,13 +181,13 @@ public class GenerateReportActionRDBMSTest extends RDBMSActionTest
assertEquals("""
"Line Item Id","Item SKU","Item Store Id","Item Store Name"
"1","QM-1","1","Q-Mart"
"5","QM-1","1","Q-Mart"
"2","QM-2","1","Q-Mart"
"3","QM-3","1","Q-Mart"
"4","QRU-1","2","QQQ 'R' Us"
"5","QM-1","1","Q-Mart"
"6","QRU-1","2","QQQ 'R' Us"
"8","QRU-1","2","QQQ 'R' Us"
"7","QRU-2","2","QQQ 'R' Us"
"8","QRU-1","2","QQQ 'R' Us"
"9","QD-1","3","QDepot"
"10","QD-1","3","QDepot"
"11","QD-1","3","QDepot"

View File

@ -79,6 +79,7 @@ INSERT INTO carrier (id, name, company_code, service_level) VALUES (9, 'USPS Sup
INSERT INTO carrier (id, name, company_code, service_level) VALUES (10, 'DHL International', 'DHL', 'I');
INSERT INTO carrier (id, name, company_code, service_level) VALUES (11, 'GSO', 'GSO', 'G');
DROP TABLE IF EXISTS line_item_extrinsic;
DROP TABLE IF EXISTS order_line;
DROP TABLE IF EXISTS item;
DROP TABLE IF EXISTS `order`;
@ -138,7 +139,7 @@ CREATE TABLE order_line
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT REFERENCES `order`,
sku VARCHAR(80),
store_id INT REFERENCES store, -- todo - as a challenge, if this field wasn't here, so we had to join through order...
store_id INT REFERENCES store,
quantity INT
);
@ -177,3 +178,12 @@ CREATE TABLE warehouse_store_int
INSERT INTO warehouse_store_int (warehouse_id, store_id) VALUES (1, 1);
INSERT INTO warehouse_store_int (warehouse_id, store_id) VALUES (1, 2);
INSERT INTO warehouse_store_int (warehouse_id, store_id) VALUES (1, 3);
CREATE TABLE line_item_extrinsic
(
id INT AUTO_INCREMENT PRIMARY KEY,
order_line_id INT REFERENCES order_line,
`key` VARCHAR(80),
`value` VARCHAR(80)
);