@@ -1354,346 +1354,6 @@ public function deleteDocument(string $collection, string $id): bool
13541354 return $ deleted ;
13551355 }
13561356
1357- /**
1358- * Find Documents
1359- *
1360- * @param Document $collection
1361- * @param array<Query> $queries
1362- * @param int|null $limit
1363- * @param int|null $offset
1364- * @param array<string> $orderAttributes
1365- * @param array<string> $orderTypes
1366- * @param array<string, mixed> $cursor
1367- * @param string $cursorDirection
1368- * @param string $forPermission
1369- * @return array<Document>
1370- * @throws DatabaseException
1371- * @throws TimeoutException
1372- * @throws Exception
1373- */
1374- public function find (Document $ collection , array $ queries = [], ?int $ limit = 25 , ?int $ offset = null , array $ orderAttributes = [], array $ orderTypes = [], array $ cursor = [], string $ cursorDirection = Database::CURSOR_AFTER , string $ forPermission = Database::PERMISSION_READ ): array
1375- {
1376- $ spatialAttributes = $ this ->getSpatialAttributes ($ collection );
1377- $ attributes = $ collection ->getAttribute ('attributes ' , []);
1378-
1379- $ collection = $ collection ->getId ();
1380- $ name = $ this ->filter ($ collection );
1381- $ roles = Authorization::getRoles ();
1382- $ where = [];
1383- $ orders = [];
1384- $ alias = Query::DEFAULT_ALIAS ;
1385- $ binds = [];
1386-
1387- $ queries = array_map (fn ($ query ) => clone $ query , $ queries );
1388-
1389- $ cursorWhere = [];
1390-
1391- foreach ($ orderAttributes as $ i => $ originalAttribute ) {
1392- $ attribute = $ this ->getInternalKeyForAttribute ($ originalAttribute );
1393- $ attribute = $ this ->filter ($ attribute );
1394-
1395- $ orderType = $ this ->filter ($ orderTypes [$ i ] ?? Database::ORDER_ASC );
1396- $ direction = $ orderType ;
1397-
1398- if ($ cursorDirection === Database::CURSOR_BEFORE ) {
1399- $ direction = ($ direction === Database::ORDER_ASC )
1400- ? Database::ORDER_DESC
1401- : Database::ORDER_ASC ;
1402- }
1403-
1404- $ orders [] = "{$ this ->quote ($ attribute )} {$ direction }" ;
1405-
1406- // Build pagination WHERE clause only if we have a cursor
1407- if (!empty ($ cursor )) {
1408- // Special case: No tie breaks. only 1 attribute and it's a unique primary key
1409- if (count ($ orderAttributes ) === 1 && $ i === 0 && $ originalAttribute === '$sequence ' ) {
1410- $ operator = ($ direction === Database::ORDER_DESC )
1411- ? Query::TYPE_LESSER
1412- : Query::TYPE_GREATER ;
1413-
1414- $ bindName = ":cursor_pk " ;
1415- $ binds [$ bindName ] = $ cursor [$ originalAttribute ];
1416-
1417- $ cursorWhere [] = "{$ this ->quote ($ alias )}. {$ this ->quote ($ attribute )} {$ this ->getSQLOperator ($ operator )} {$ bindName }" ;
1418- break ;
1419- }
1420-
1421- $ conditions = [];
1422-
1423- // Add equality conditions for previous attributes
1424- for ($ j = 0 ; $ j < $ i ; $ j ++) {
1425- $ prevOriginal = $ orderAttributes [$ j ];
1426- $ prevAttr = $ this ->filter ($ this ->getInternalKeyForAttribute ($ prevOriginal ));
1427-
1428- $ bindName = ":cursor_ {$ j }" ;
1429- $ binds [$ bindName ] = $ cursor [$ prevOriginal ];
1430-
1431- $ conditions [] = "{$ this ->quote ($ alias )}. {$ this ->quote ($ prevAttr )} = {$ bindName }" ;
1432- }
1433-
1434- // Add comparison for current attribute
1435- $ operator = ($ direction === Database::ORDER_DESC )
1436- ? Query::TYPE_LESSER
1437- : Query::TYPE_GREATER ;
1438-
1439- $ bindName = ":cursor_ {$ i }" ;
1440- $ binds [$ bindName ] = $ cursor [$ originalAttribute ];
1441-
1442- $ conditions [] = "{$ this ->quote ($ alias )}. {$ this ->quote ($ attribute )} {$ this ->getSQLOperator ($ operator )} {$ bindName }" ;
1443-
1444- $ cursorWhere [] = '( ' . implode (' AND ' , $ conditions ) . ') ' ;
1445- }
1446- }
1447-
1448- if (!empty ($ cursorWhere )) {
1449- $ where [] = '( ' . implode (' OR ' , $ cursorWhere ) . ') ' ;
1450- }
1451-
1452- $ conditions = $ this ->getSQLConditions ($ queries , $ binds , attributes:$ attributes );
1453- if (!empty ($ conditions )) {
1454- $ where [] = $ conditions ;
1455- }
1456-
1457- if (Authorization::$ status ) {
1458- $ where [] = $ this ->getSQLPermissionsCondition ($ name , $ roles , $ alias , $ forPermission );
1459- }
1460-
1461- if ($ this ->sharedTables ) {
1462- $ binds [':_tenant ' ] = $ this ->tenant ;
1463- $ where [] = "{$ this ->getTenantQuery ($ collection , $ alias , condition: '' )}" ;
1464- }
1465-
1466- $ sqlWhere = !empty ($ where ) ? 'WHERE ' . implode (' AND ' , $ where ) : '' ;
1467- $ sqlOrder = 'ORDER BY ' . implode (', ' , $ orders );
1468-
1469- $ sqlLimit = '' ;
1470- if (! \is_null ($ limit )) {
1471- $ binds [':limit ' ] = $ limit ;
1472- $ sqlLimit = 'LIMIT :limit ' ;
1473- }
1474-
1475- if (! \is_null ($ offset )) {
1476- $ binds [':offset ' ] = $ offset ;
1477- $ sqlLimit .= ' OFFSET :offset ' ;
1478- }
1479-
1480- $ selections = $ this ->getAttributeSelections ($ queries );
1481-
1482-
1483- $ sql = "
1484- SELECT {$ this ->getAttributeProjection ($ selections , $ alias , $ spatialAttributes )}
1485- FROM {$ this ->getSQLTable ($ name )} AS {$ this ->quote ($ alias )}
1486- {$ sqlWhere }
1487- {$ sqlOrder }
1488- {$ sqlLimit };
1489- " ;
1490-
1491- $ sql = $ this ->trigger (Database::EVENT_DOCUMENT_FIND , $ sql );
1492-
1493- try {
1494- $ stmt = $ this ->getPDO ()->prepare ($ sql );
1495-
1496- foreach ($ binds as $ key => $ value ) {
1497- if (gettype ($ value ) === 'double ' ) {
1498- $ stmt ->bindValue ($ key , $ this ->getFloatPrecision ($ value ), PDO ::PARAM_STR );
1499- } else {
1500- $ stmt ->bindValue ($ key , $ value , $ this ->getPDOType ($ value ));
1501- }
1502- }
1503-
1504- $ stmt ->execute ();
1505- } catch (PDOException $ e ) {
1506- throw $ this ->processException ($ e );
1507- }
1508-
1509- $ results = $ stmt ->fetchAll ();
1510- $ stmt ->closeCursor ();
1511-
1512- foreach ($ results as $ index => $ document ) {
1513- if (\array_key_exists ('_uid ' , $ document )) {
1514- $ results [$ index ]['$id ' ] = $ document ['_uid ' ];
1515- unset($ results [$ index ]['_uid ' ]);
1516- }
1517- if (\array_key_exists ('_id ' , $ document )) {
1518- $ results [$ index ]['$sequence ' ] = $ document ['_id ' ];
1519- unset($ results [$ index ]['_id ' ]);
1520- }
1521- if (\array_key_exists ('_tenant ' , $ document )) {
1522- $ results [$ index ]['$tenant ' ] = $ document ['_tenant ' ];
1523- unset($ results [$ index ]['_tenant ' ]);
1524- }
1525- if (\array_key_exists ('_createdAt ' , $ document )) {
1526- $ results [$ index ]['$createdAt ' ] = $ document ['_createdAt ' ];
1527- unset($ results [$ index ]['_createdAt ' ]);
1528- }
1529- if (\array_key_exists ('_updatedAt ' , $ document )) {
1530- $ results [$ index ]['$updatedAt ' ] = $ document ['_updatedAt ' ];
1531- unset($ results [$ index ]['_updatedAt ' ]);
1532- }
1533- if (\array_key_exists ('_permissions ' , $ document )) {
1534- $ results [$ index ]['$permissions ' ] = \json_decode ($ document ['_permissions ' ] ?? '[] ' , true );
1535- unset($ results [$ index ]['_permissions ' ]);
1536- }
1537-
1538- $ results [$ index ] = new Document ($ results [$ index ]);
1539- }
1540-
1541- if ($ cursorDirection === Database::CURSOR_BEFORE ) {
1542- $ results = \array_reverse ($ results );
1543- }
1544-
1545- return $ results ;
1546- }
1547-
1548- /**
1549- * Count Documents
1550- *
1551- * @param Document $collection
1552- * @param array<Query> $queries
1553- * @param int|null $max
1554- * @return int
1555- * @throws Exception
1556- * @throws PDOException
1557- */
1558- public function count (Document $ collection , array $ queries = [], ?int $ max = null ): int
1559- {
1560- $ attributes = $ collection ->getAttribute ("attributes " , []);
1561- $ collection = $ collection ->getId ();
1562- $ name = $ this ->filter ($ collection );
1563- $ roles = Authorization::getRoles ();
1564- $ binds = [];
1565- $ where = [];
1566- $ alias = Query::DEFAULT_ALIAS ;
1567-
1568- $ limit = '' ;
1569- if (! \is_null ($ max )) {
1570- $ binds [':limit ' ] = $ max ;
1571- $ limit = 'LIMIT :limit ' ;
1572- }
1573-
1574- $ queries = array_map (fn ($ query ) => clone $ query , $ queries );
1575-
1576- $ conditions = $ this ->getSQLConditions ($ queries , $ binds , attributes:$ attributes );
1577- if (!empty ($ conditions )) {
1578- $ where [] = $ conditions ;
1579- }
1580-
1581- if (Authorization::$ status ) {
1582- $ where [] = $ this ->getSQLPermissionsCondition ($ name , $ roles , $ alias );
1583- }
1584-
1585- if ($ this ->sharedTables ) {
1586- $ binds [':_tenant ' ] = $ this ->tenant ;
1587- $ where [] = "{$ this ->getTenantQuery ($ collection , $ alias , condition: '' )}" ;
1588- }
1589-
1590- $ sqlWhere = !empty ($ where )
1591- ? 'WHERE ' . \implode (' AND ' , $ where )
1592- : '' ;
1593-
1594- $ sql = "
1595- SELECT COUNT(1) as sum FROM (
1596- SELECT 1
1597- FROM {$ this ->getSQLTable ($ name )} AS {$ this ->quote ($ alias )}
1598- {$ sqlWhere }
1599- {$ limit }
1600- ) table_count
1601- " ;
1602-
1603- $ sql = $ this ->trigger (Database::EVENT_DOCUMENT_COUNT , $ sql );
1604-
1605- $ stmt = $ this ->getPDO ()->prepare ($ sql );
1606-
1607- foreach ($ binds as $ key => $ value ) {
1608- $ stmt ->bindValue ($ key , $ value , $ this ->getPDOType ($ value ));
1609- }
1610-
1611- $ stmt ->execute ();
1612-
1613- $ result = $ stmt ->fetchAll ();
1614- $ stmt ->closeCursor ();
1615- if (!empty ($ result )) {
1616- $ result = $ result [0 ];
1617- }
1618-
1619- return $ result ['sum ' ] ?? 0 ;
1620- }
1621-
1622- /**
1623- * Sum an Attribute
1624- *
1625- * @param Document $collection
1626- * @param string $attribute
1627- * @param array<Query> $queries
1628- * @param int|null $max
1629- * @return int|float
1630- * @throws Exception
1631- * @throws PDOException
1632- */
1633- public function sum (Document $ collection , string $ attribute , array $ queries = [], ?int $ max = null ): int |float
1634- {
1635- $ collectionAttributes = $ collection ->getAttribute ("attributes " , []);
1636- $ collection = $ collection ->getId ();
1637- $ name = $ this ->filter ($ collection );
1638- $ roles = Authorization::getRoles ();
1639- $ where = [];
1640- $ alias = Query::DEFAULT_ALIAS ;
1641- $ binds = [];
1642-
1643- $ limit = '' ;
1644- if (! \is_null ($ max )) {
1645- $ binds [':limit ' ] = $ max ;
1646- $ limit = 'LIMIT :limit ' ;
1647- }
1648-
1649- $ queries = array_map (fn ($ query ) => clone $ query , $ queries );
1650-
1651- $ conditions = $ this ->getSQLConditions ($ queries , $ binds , attributes:$ collectionAttributes );
1652- if (!empty ($ conditions )) {
1653- $ where [] = $ conditions ;
1654- }
1655-
1656- if (Authorization::$ status ) {
1657- $ where [] = $ this ->getSQLPermissionsCondition ($ name , $ roles , $ alias );
1658- }
1659-
1660- if ($ this ->sharedTables ) {
1661- $ binds [':_tenant ' ] = $ this ->tenant ;
1662- $ where [] = "{$ this ->getTenantQuery ($ collection , $ alias , condition: '' )}" ;
1663- }
1664-
1665- $ sqlWhere = !empty ($ where )
1666- ? 'WHERE ' . \implode (' AND ' , $ where )
1667- : '' ;
1668-
1669- $ sql = "
1670- SELECT SUM( {$ this ->quote ($ attribute )}) as sum FROM (
1671- SELECT {$ this ->quote ($ attribute )}
1672- FROM {$ this ->getSQLTable ($ name )} AS {$ this ->quote ($ alias )}
1673- {$ sqlWhere }
1674- {$ limit }
1675- ) table_count
1676- " ;
1677-
1678- $ sql = $ this ->trigger (Database::EVENT_DOCUMENT_SUM , $ sql );
1679-
1680- $ stmt = $ this ->getPDO ()->prepare ($ sql );
1681-
1682- foreach ($ binds as $ key => $ value ) {
1683- $ stmt ->bindValue ($ key , $ value , $ this ->getPDOType ($ value ));
1684- }
1685-
1686- $ stmt ->execute ();
1687-
1688- $ result = $ stmt ->fetchAll ();
1689- $ stmt ->closeCursor ();
1690- if (!empty ($ result )) {
1691- $ result = $ result [0 ];
1692- }
1693-
1694- return $ result ['sum ' ] ?? 0 ;
1695- }
1696-
16971357 /**
16981358 * Handle spatial queries
16991359 *
0 commit comments