public function queryObjects(
        $typeId,
        $where = null,
        $searchAllVersions = false,
        OperationContextInterface $context = null
    ) {
        if (empty($typeId)) {
            throw new CmisInvalidArgumentException('Type id must not be empty.');
        }

        if ($context === null) {
            $context = $this->getDefaultContext();
        }

        $queryFilterString = $context->getQueryFilterString();
        if (!empty($queryFilterString)) {
            $querySelect = $queryFilterString;
        } else {
            $querySelect = '*';
        }

        $whereClause = '';
        if (!empty($where)) {
            $whereClause = ' WHERE ' . $where;
        }

        $orderBy = $context->getOrderBy();
        if (!empty($orderBy)) {
            $orderBy = ' ORDER BY ' . $orderBy;
        }

        $typeDefinition = $this->getTypeDefinition($typeId);
        $statement = 'SELECT ' . $querySelect . ' FROM ' . $typeDefinition->getQueryName() . $whereClause . $orderBy;
        $queryStatement = new QueryStatement($this, $statement);

        $resultObjects = [];
        $skipCount = 0;

        $objectList = $this->getBinding()->getDiscoveryService()->query(
            $this->getRepositoryInfo()->getId(),
            $queryStatement->toQueryString(),
            $searchAllVersions,
            $context->getIncludeRelationships(),
            $context->getRenditionFilterString(),
            $context->isIncludeAllowableActions(),
            $context->getMaxItemsPerPage(),
            $skipCount
        );

        foreach ($objectList->getObjects() as $objectData) {
            $object = $this->getObjectFactory()->convertObject($objectData, $context);
            if ($object instanceof CmisObjectInterface) {
                $resultObjects[] = $object;
            }
        }

        // TODO: Implement pagination using skipCount

        return $resultObjects;
    }