public function query(
        $expression,
        array $properties,
        array $orderBy = null,
        $limit = null,
        $offset = 0
    ) {
        // Ensure "id" property specified.
        $properties = array_flip($properties);
        $properties['id'] = true;
        $properties = array_keys($properties);

        // Set default ordering if needed.
        if (!$orderBy) {
            $orderBy = ['id' => 'Ascending'];
        }

        // Prepare request payload.
        $payload = [
            'expression' => (string) $expression,
            'sortByColumn' => key($orderBy),
            'sortDirection' => reset($orderBy),
            'properties' => $properties
        ];

        $this->getLogger()->debug('[ItemService::query] Payload: ' . json_encode($payload));

        $url = $this->getBase() . 's/Query?rangeSelector.skip=' . $offset;
        if ($limit) {
            $url .= '&rangeSelector.take=' . $limit;
        }
        $response = $this->client->sendRequest($url, 'POST', $payload);
        try {
            $dataSet = new PropertySetStruct($response);
        } catch (\InvalidArgumentException $e) {
            throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
        }

        $result = [];
        $items = $dataSet->exportItems();
        foreach ($items as $item) {
            try {
                $result[] = new Item($item);
            } catch (\InvalidArgumentException $e) {
                throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
            }
        }

        return $result;
    }