| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\DataAccess; |
|
|
| use Exception; |
| use Piwik\Archive; |
| use Piwik\Archive\Chunk; |
| use Piwik\ArchiveProcessor; |
| use Piwik\ArchiveProcessor\Rules; |
| use Piwik\Common; |
| use Piwik\Container\StaticContainer; |
| use Piwik\Date; |
| use Piwik\Db; |
| use Piwik\Period; |
| use Piwik\Period\Range; |
| use Piwik\Segment; |
| use Piwik\Log\LoggerInterface; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class ArchiveSelector |
| { |
| public const NB_VISITS_RECORD_LOOKED_UP = "nb_visits"; |
|
|
| public const NB_VISITS_CONVERTED_RECORD_LOOKED_UP = "nb_visits_converted"; |
|
|
| private static function getModel() |
| { |
| return new Model(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static function getArchiveIdAndVisits(ArchiveProcessor\Parameters $params, $minDatetimeArchiveProcessedUTC = false, $includeInvalidated = null) |
| { |
| $idSite = $params->getSite()->getId(); |
| $period = $params->getPeriod()->getId(); |
| $dateStart = $params->getPeriod()->getDateStart(); |
| $dateStartIso = $dateStart->toString('Y-m-d'); |
| $dateEndIso = $params->getPeriod()->getDateEnd()->toString('Y-m-d'); |
|
|
| $numericTable = ArchiveTableCreator::getNumericTable($dateStart, false); |
| if (empty($numericTable)) { |
| return self::archiveInfoBcResult([ |
| 'idArchives' => false, |
| 'visits' => false, |
| 'visitsConverted' => false, |
| 'archiveExists' => false, |
| 'tsArchived' => false, |
| 'doneFlagValue' => false, |
| 'existingRecords' => null, |
| ]); |
| } |
|
|
| $requestedPlugin = $params->getRequestedPlugin(); |
| $requestedReport = $params->getArchiveOnlyReport(); |
|
|
| $segment = $params->getSegment(); |
| $plugins = array("VisitsSummary", $requestedPlugin); |
| $plugins = array_filter($plugins); |
|
|
| $doneFlags = Rules::getDoneFlags($plugins, $segment); |
|
|
| $requestedPluginDoneFlags = empty($requestedPlugin) ? [] : Rules::getDoneFlags([$requestedPlugin], $segment); |
| $allPluginsDoneFlag = Rules::getDoneFlagArchiveContainsAllPlugins($segment); |
|
|
| $doneFlagValues = Rules::getSelectableDoneFlagValues($includeInvalidated === null ? true : $includeInvalidated, $params, $includeInvalidated === null); |
|
|
| $results = self::getModel()->getArchiveIdAndVisits($numericTable, $idSite, $period, $dateStartIso, $dateEndIso, null, $doneFlags); |
| if (empty($results)) { |
| return self::archiveInfoBcResult([ |
| 'idArchives' => false, |
| 'visits' => false, |
| 'visitsConverted' => false, |
| 'archiveExists' => false, |
| 'tsArchived' => false, |
| 'doneFlagValue' => false, |
| 'existingRecords' => null, |
| ]); |
| } |
|
|
| $result = self::findArchiveDataWithLatestTsArchived($results, $requestedPluginDoneFlags, $allPluginsDoneFlag); |
|
|
| $tsArchived = isset($result['ts_archived']) ? $result['ts_archived'] : false; |
| $visits = isset($result['nb_visits']) ? $result['nb_visits'] : false; |
| $visitsConverted = isset($result['nb_visits_converted']) ? $result['nb_visits_converted'] : false; |
| $value = isset($result['value']) ? $result['value'] : false; |
|
|
| $existingRecords = null; |
|
|
| $result['idarchive'] = empty($result['idarchive']) ? [] : [$result['idarchive']]; |
| if (!empty($result['partial'])) { |
| |
| |
| |
| |
| |
| |
| |
| |
| if ( |
| empty($requestedReport) |
| || !empty($result['idarchive']) |
| ) { |
| $result['idarchive'] = array_merge($result['idarchive'], $result['partial']); |
| } else { |
| $existingRecords = self::getModel()->getRecordsContainedInArchives($dateStart, $result['partial'], $requestedReport); |
| if (!empty($existingRecords)) { |
| $result['idarchive'] = array_merge($result['idarchive'], $result['partial']); |
| } |
| } |
| } |
|
|
| if ( |
| empty($result['idarchive']) |
| || (isset($result['value']) |
| && !in_array($result['value'], $doneFlagValues)) |
| ) { |
| return self::archiveInfoBcResult([ |
| 'idArchives' => false, |
| 'visits' => $visits, |
| 'visitsConverted' => $visitsConverted, |
| 'archiveExists' => true, |
| 'tsArchived' => $tsArchived, |
| 'doneFlagValue' => $value, |
| 'existingRecords' => null, |
| ]); |
| } |
|
|
| if (!empty($minDatetimeArchiveProcessedUTC) && !is_object($minDatetimeArchiveProcessedUTC)) { |
| $minDatetimeArchiveProcessedUTC = Date::factory($minDatetimeArchiveProcessedUTC); |
| } |
|
|
| |
| if ( |
| $minDatetimeArchiveProcessedUTC |
| && !empty($result['idarchive']) |
| && Date::factory($tsArchived)->isEarlier($minDatetimeArchiveProcessedUTC) |
| ) { |
| return self::archiveInfoBcResult([ |
| 'idArchives' => false, |
| 'visits' => $visits, |
| 'visitsConverted' => $visitsConverted, |
| 'archiveExists' => true, |
| 'tsArchived' => $tsArchived, |
| 'doneFlagValue' => $value, |
| 'existingRecords' => null, |
| ]); |
| } |
|
|
| $idArchives = !empty($result['idarchive']) ? $result['idarchive'] : false; |
|
|
| return self::archiveInfoBcResult([ |
| 'idArchives' => $idArchives, |
| 'visits' => $visits, |
| 'visitsConverted' => $visitsConverted, |
| 'archiveExists' => true, |
| 'tsArchived' => $tsArchived, |
| 'doneFlagValue' => $value, |
| 'existingRecords' => $existingRecords, |
| ]); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static function getArchiveIds( |
| $siteIds, |
| $periods, |
| $segment, |
| $plugins, |
| $includeInvalidated = true, |
| $_skipSetGroupConcatMaxLen = false |
| ) { |
| return self::getArchiveIdsAndStates( |
| $siteIds, |
| $periods, |
| $segment, |
| $plugins, |
| $includeInvalidated, |
| $_skipSetGroupConcatMaxLen |
| )[0]; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static function getArchiveIdsAndStates( |
| $siteIds, |
| $periods, |
| $segment, |
| $plugins, |
| $includeInvalidated = true, |
| $_skipSetGroupConcatMaxLen = false |
| ): array { |
| $logger = StaticContainer::get(LoggerInterface::class); |
|
|
| if (!$_skipSetGroupConcatMaxLen) { |
| try { |
| Db::get()->query('SET SESSION group_concat_max_len=' . (128 * 1024)); |
| } catch (\Exception $ex) { |
| $logger->info("Could not set group_concat_max_len MySQL session variable."); |
| } |
| } |
|
|
| if (empty($siteIds)) { |
| throw new \Exception("Website IDs could not be read from the request, ie. idSite="); |
| } |
|
|
| foreach ($siteIds as $index => $siteId) { |
| $siteIds[$index] = (int) $siteId; |
| } |
|
|
| $getArchiveIdsSql = "SELECT idsite, date1, date2, |
| GROUP_CONCAT(CONCAT(idarchive,'|',`name`,'|',`value`) ORDER BY idarchive DESC SEPARATOR ',') AS archives |
| FROM `%s` |
| WHERE idsite IN (" . implode(',', $siteIds) . ") |
| AND " . self::getNameCondition($plugins, $segment, $includeInvalidated) . " |
| AND %s |
| GROUP BY idsite, date1, date2"; |
|
|
| $monthToPeriods = array(); |
| foreach ($periods as $period) { |
| |
| if ($period->getDateStart()->isLater(Date::now()->addDay(2))) { |
| continue; |
| } |
| $table = ArchiveTableCreator::getNumericTable($period->getDateStart(), false); |
| if (empty($table)) { |
| continue; |
| } |
| $monthToPeriods[$table][] = $period; |
| } |
|
|
| $db = Db::get(); |
|
|
| |
| $idarchives = []; |
| $idarchiveStates = []; |
|
|
| foreach ($monthToPeriods as $table => $periods) { |
| $firstPeriod = reset($periods); |
| $bind = []; |
|
|
| if ($firstPeriod instanceof Range) { |
| $dateCondition = "date1 = ? AND date2 = ?"; |
| $bind[] = $firstPeriod->getDateStart()->toString('Y-m-d'); |
| $bind[] = $firstPeriod->getDateEnd()->toString('Y-m-d'); |
| } else { |
| |
| $dateCondition = '('; |
|
|
| foreach ($periods as $period) { |
| if (strlen($dateCondition) > 1) { |
| $dateCondition .= ' OR '; |
| } |
|
|
| $dateCondition .= "(period = ? AND date1 = ? AND date2 = ?)"; |
| $bind[] = $period->getId(); |
| $bind[] = $period->getDateStart()->toString('Y-m-d'); |
| $bind[] = $period->getDateEnd()->toString('Y-m-d'); |
| } |
|
|
| $dateCondition .= ')'; |
| } |
|
|
| $sql = sprintf($getArchiveIdsSql, $table, $dateCondition); |
| $archiveIds = $db->fetchAll($sql, $bind); |
|
|
| |
| |
| foreach ($archiveIds as $row) { |
| $dateStr = $row['date1'] . ',' . $row['date2']; |
| $idSite = $row['idsite']; |
|
|
| $archives = $row['archives']; |
| $pairs = explode(',', $archives); |
|
|
| foreach ($pairs as $pair) { |
| $parts = explode('|', $pair); |
|
|
| if (count($parts) != 3) { |
| |
| |
| $logger->info("GROUP_CONCAT got cut off in ArchiveSelector." . __FUNCTION__ . ' for idsite = ' . $idSite . ', period = ' . $dateStr); |
| continue; |
| } |
|
|
| [$idarchive, $doneFlag, $value] = $parts; |
|
|
| $idarchives[$doneFlag][$dateStr][] = $idarchive; |
| $idarchiveStates[$idSite][$doneFlag][$dateStr][$idarchive] = (int) $value; |
|
|
| if ( |
| strpos($doneFlag, '.') === false |
| |
| |
| && $value != ArchiveWriter::DONE_PARTIAL |
| ) { |
| break; |
| } |
| } |
| } |
| } |
|
|
| return [$idarchives, $idarchiveStates]; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static function getArchiveData($archiveIds, $recordNames, $archiveDataType, $idSubtable) |
| { |
| $chunk = new Chunk(); |
| $db = Db::get(); |
|
|
| $loadAllSubtables = $idSubtable === Archive::ID_SUBTABLE_LOAD_ALL_SUBTABLES; |
| [$getValuesSql, $bind] = self::getSqlTemplateToFetchArchiveData($recordNames, $idSubtable); |
|
|
| $archiveIdsPerMonth = self::getArchiveIdsByYearMonth($archiveIds); |
|
|
| |
| $rows = array(); |
| foreach ($archiveIdsPerMonth as $yearMonth => $ids) { |
| if (empty($ids)) { |
| throw new Exception("Unexpected: id archive not found for period '$yearMonth' '"); |
| } |
|
|
| |
| $date = Date::factory($yearMonth . '-01'); |
|
|
| $isNumeric = $archiveDataType === 'numeric'; |
| if ($isNumeric) { |
| $table = ArchiveTableCreator::getNumericTable($date, false); |
| } else { |
| $table = ArchiveTableCreator::getBlobTable($date, false); |
| } |
|
|
| if (empty($table)) { |
| continue; |
| } |
|
|
| $ids = array_map('intval', $ids); |
| $sql = sprintf($getValuesSql, $table, implode(',', $ids)); |
| $dataRows = $db->fetchAll($sql, $bind); |
|
|
| foreach ($dataRows as $row) { |
| if ($isNumeric) { |
| $rows[] = $row; |
| } else { |
| $row['value'] = self::uncompress($row['value']); |
|
|
| if ($chunk->isRecordNameAChunk($row['name'])) { |
| self::moveChunkRowToRows($rows, $row, $chunk, $loadAllSubtables, $idSubtable); |
| } else { |
| $rows[] = $row; |
| } |
| } |
| } |
| } |
|
|
| return $rows; |
| } |
|
|
| private static function moveChunkRowToRows(&$rows, $row, Chunk $chunk, $loadAllSubtables, $idSubtable) |
| { |
| |
| $blobs = Common::safe_unserialize($row['value']); |
|
|
| if (!is_array($blobs)) { |
| return; |
| } |
|
|
| |
| $rawName = $chunk->getRecordNameWithoutChunkAppendix($row['name']); |
|
|
| if ($loadAllSubtables) { |
| foreach ($blobs as $subtableId => $blob) { |
| $row['value'] = $blob; |
| $row['name'] = self::appendIdSubtable($rawName, $subtableId); |
| $rows[] = $row; |
| } |
| } elseif (array_key_exists($idSubtable, $blobs)) { |
| $row['value'] = $blobs[$idSubtable]; |
| $row['name'] = self::appendIdSubtable($rawName, $idSubtable); |
| $rows[] = $row; |
| } |
| } |
|
|
| public static function appendIdSubtable($recordName, $id) |
| { |
| return $recordName . "_" . $id; |
| } |
|
|
| public static function uncompress($data) |
| { |
| return @gzuncompress($data); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| private static function getNameCondition(array $plugins, Segment $segment, $includeInvalidated = true) |
| { |
| |
| |
| $doneFlags = Rules::getDoneFlags($plugins, $segment); |
| $allDoneFlags = "'" . implode("','", $doneFlags) . "'"; |
|
|
| $possibleValues = Rules::getSelectableDoneFlagValues($includeInvalidated, null, $checkAuthorizedToArchive = false); |
|
|
| |
| return "((name IN ($allDoneFlags)) AND (value IN (" . implode(',', $possibleValues) . ")))"; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private static function findArchiveDataWithLatestTsArchived($results, $requestedPluginDoneFlags, $allPluginsDoneFlag) |
| { |
| $doneFlags = array_merge($requestedPluginDoneFlags, [$allPluginsDoneFlag]); |
|
|
| |
| $idArchives = []; |
| $tsArchiveds = []; |
| foreach ($results as $row) { |
| $doneFlag = $row['name']; |
| if (!isset($idArchives[$doneFlag])) { |
| $idArchives[$doneFlag] = $row['idarchive']; |
| $tsArchiveds[$doneFlag] = $row['ts_archived']; |
| } |
| } |
|
|
| $archiveData = [ |
| self::NB_VISITS_RECORD_LOOKED_UP => false, |
| self::NB_VISITS_CONVERTED_RECORD_LOOKED_UP => false, |
| ]; |
|
|
| foreach ($results as $result) { |
| if ( |
| in_array($result['name'], $doneFlags) |
| && in_array($result['idarchive'], $idArchives) |
| && $result['value'] != ArchiveWriter::DONE_PARTIAL |
| ) { |
| $archiveData = $result; |
| if (empty($archiveData[self::NB_VISITS_RECORD_LOOKED_UP])) { |
| $archiveData[self::NB_VISITS_RECORD_LOOKED_UP] = 0; |
| } |
| if (empty($archiveData[self::NB_VISITS_CONVERTED_RECORD_LOOKED_UP])) { |
| $archiveData[self::NB_VISITS_CONVERTED_RECORD_LOOKED_UP] = 0; |
| } |
| break; |
| } |
| } |
|
|
| foreach ([self::NB_VISITS_RECORD_LOOKED_UP, self::NB_VISITS_CONVERTED_RECORD_LOOKED_UP] as $metric) { |
| foreach ($results as $result) { |
| if (!in_array($result['idarchive'], $idArchives)) { |
| continue; |
| } |
|
|
| if (empty($archiveData[$metric])) { |
| if (!empty($result[$metric]) || $result[$metric] === 0 || $result[$metric] === 0.0 || $result[$metric] === '0') { |
| $archiveData[$metric] = $result[$metric]; |
| } |
| } |
| } |
| } |
|
|
| |
| $mainTsArchived = isset($tsArchiveds[$allPluginsDoneFlag]) ? $tsArchiveds[$allPluginsDoneFlag] : null; |
| foreach ($results as $row) { |
| if (!isset($idArchives[$row['name']])) { |
| continue; |
| } |
|
|
| $thisTsArchived = Date::factory($row['ts_archived']); |
| if ( |
| $row['value'] == ArchiveWriter::DONE_PARTIAL |
| && (empty($mainTsArchived) || !Date::factory($mainTsArchived)->isLater($thisTsArchived)) |
| ) { |
| $archiveData['partial'][] = $row['idarchive']; |
|
|
| if (empty($archiveData['ts_archived'])) { |
| $archiveData['ts_archived'] = $row['ts_archived']; |
| } |
| } |
| } |
|
|
| return $archiveData; |
| } |
|
|
| |
| |
| |
| |
| |
| private static function archiveInfoBcResult(array $archiveInfo) |
| { |
| $archiveInfo[0] = $archiveInfo['idArchives']; |
| $archiveInfo[1] = $archiveInfo['visits']; |
| $archiveInfo[2] = $archiveInfo['visitsConverted']; |
| $archiveInfo[3] = $archiveInfo['archiveExists']; |
| $archiveInfo[4] = $archiveInfo['tsArchived']; |
| $archiveInfo[5] = $archiveInfo['doneFlagValue']; |
| return $archiveInfo; |
| } |
|
|
| public static function querySingleBlob(array $archiveIds, string $recordName) |
| { |
| $chunk = new Chunk(); |
|
|
| [$getValuesSql, $bind] = self::getSqlTemplateToFetchArchiveData( |
| [$recordName], |
| Archive::ID_SUBTABLE_LOAD_ALL_SUBTABLES, |
| true |
| ); |
|
|
| $archiveIdsPerMonth = self::getArchiveIdsByYearMonth($archiveIds); |
|
|
| $periodsSeen = []; |
|
|
| |
| foreach ($archiveIdsPerMonth as $yearMonth => $ids) { |
| $date = Date::factory($yearMonth . '-01'); |
|
|
| $table = ArchiveTableCreator::getBlobTable($date, false); |
| if (empty($table)) { |
| continue; |
| } |
|
|
| $ids = array_map('intval', $ids); |
| $sql = sprintf($getValuesSql, $table, implode(',', $ids)); |
|
|
| $cursor = Db::get()->query($sql, $bind); |
| while ($row = $cursor->fetch()) { |
| $period = $row['date1'] . ',' . $row['date2']; |
| $recordName = $row['name']; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| if (empty($archiveIds[$period])) { |
| continue; |
| } |
|
|
| |
| if (!empty($periodsSeen[$period][$recordName])) { |
| continue; |
| } |
|
|
| $periodsSeen[$period][$recordName] = true; |
|
|
| $row['value'] = ArchiveSelector::uncompress($row['value']); |
| if ($chunk->isRecordNameAChunk($row['name'])) { |
| |
| $blobs = Common::safe_unserialize($row['value']); |
| if (!is_array($blobs)) { |
| yield $row; |
| } |
|
|
| ksort($blobs); |
|
|
| |
| $rawName = $chunk->getRecordNameWithoutChunkAppendix($row['name']); |
| foreach ($blobs as $subtableId => $blob) { |
| unset($blobs[$subtableId]); |
| yield array_merge($row, [ |
| 'value' => $blob, |
| 'name' => ArchiveSelector::appendIdSubtable($rawName, $subtableId), |
| ]); |
| } |
| } else { |
| yield $row; |
| } |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private static function getSqlTemplateToFetchArchiveData(array $recordNames, $idSubtable, $orderBySubtableId = false) |
| { |
| $chunk = new Chunk(); |
|
|
| $orderBy = 'ORDER BY ts_archived ASC'; |
|
|
| |
| $loadAllSubtables = $idSubtable === Archive::ID_SUBTABLE_LOAD_ALL_SUBTABLES; |
| if ($loadAllSubtables) { |
| $name = reset($recordNames); |
|
|
| |
| $nameEnd = strlen($name) + 1; |
| $nameEndAppendix = $nameEnd + 1; |
| $appendix = $chunk->getAppendix(); |
| $lenAppendix = strlen($appendix); |
|
|
| $checkForChunkBlob = "SUBSTRING(name, $nameEnd, $lenAppendix) = '$appendix'"; |
| $checkForSubtableId = "(SUBSTRING(name, $nameEndAppendix, 1) >= '0' |
| AND SUBSTRING(name, $nameEndAppendix, 1) <= '9')"; |
|
|
| $whereNameIs = "(name = ? OR (name LIKE ? AND ( $checkForChunkBlob OR $checkForSubtableId ) ))"; |
| $bind = array($name, addcslashes($name, '%_') . '%'); |
|
|
| if ($orderBySubtableId && count($recordNames) == 1) { |
| $idSubtableAsInt = self::getExtractIdSubtableFromBlobNameSql($chunk, $name); |
|
|
| $orderBy = "ORDER BY date1 ASC, " . |
| " $idSubtableAsInt ASC, |
| ts_archived DESC"; |
| } |
| } else { |
| if ($idSubtable === null) { |
| |
| $bind = array_values($recordNames); |
| } else { |
| |
| $bind = array(); |
| foreach ($recordNames as $recordName) { |
| |
| |
| $bind[] = $chunk->getRecordNameForTableId($recordName, $idSubtable); |
| $bind[] = self::appendIdSubtable($recordName, $idSubtable); |
| } |
| } |
|
|
| $inNames = Common::getSqlStringFieldsArray($bind); |
| $whereNameIs = "name IN ($inNames)"; |
| } |
|
|
| $getValuesSql = "SELECT value, name, idsite, date1, date2, ts_archived |
| FROM `%s` |
| WHERE idarchive IN (%s) |
| AND " . $whereNameIs . " |
| $orderBy"; |
|
|
| return [$getValuesSql, $bind]; |
| } |
|
|
| private static function getArchiveIdsByYearMonth(array $archiveIds) |
| { |
| |
| |
| |
| $archiveIdsPerMonth = []; |
| foreach ($archiveIds as $period => $ids) { |
| $yearMonth = substr($period, 0, 7); |
| if (empty($archiveIdsPerMonth[$yearMonth])) { |
| $archiveIdsPerMonth[$yearMonth] = []; |
| } |
| $archiveIdsPerMonth[$yearMonth] = array_merge($archiveIdsPerMonth[$yearMonth], $ids); |
| } |
| return $archiveIdsPerMonth; |
| } |
|
|
| |
| public static function getExtractIdSubtableFromBlobNameSql(Chunk $chunk, $name) |
| { |
| |
| $nameEnd = strlen($name) + 1; |
| $nameEndAfterUnderscore = $nameEnd + 1; |
| $appendix = $chunk->getAppendix(); |
| $lenAppendix = strlen($appendix); |
| $chunkEnd = $nameEnd + $lenAppendix; |
|
|
| $checkForChunkBlob = "SUBSTRING(name, $nameEnd, $lenAppendix) = '$appendix'"; |
|
|
| $extractSuffix = "SUBSTRING(name, IF($checkForChunkBlob, $chunkEnd, $nameEndAfterUnderscore))"; |
| $locateSecondUnderscore = "IF((@secondunderscore := LOCATE('_', $extractSuffix) - 1) < 0, LENGTH(name), @secondunderscore)"; |
| $extractIdSubtableStart = "IF( (@idsubtable := SUBSTRING($extractSuffix, 1, $locateSecondUnderscore)) = '', -1, @idsubtable )"; |
| $idSubtableAsInt = "CAST($extractIdSubtableStart AS SIGNED)"; |
|
|
| return $idSubtableAsInt; |
| } |
| } |
|
|