From c7b916f10f31d52617f8ac24265b8813cfede753 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Fri, 1 Apr 2022 19:32:56 +0200 Subject: [PATCH 1/2] postgres 14: EXTRACT returns a numeric, which is converted to a string. We force this to stay a double precision number (postgres 13) -- issue #5824 --- src/packages/database/pool/util.ts | 11 ++++++++++- src/packages/database/postgres-blobs.coffee | 10 +--------- .../next/pages/share/public_paths/page/[page].tsx | 4 ++-- .../server/projects/control/stop-idle-projects.ts | 3 ++- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/packages/database/pool/util.ts b/src/packages/database/pool/util.ts index 4d33c5d6cd..3a18ca7df3 100644 --- a/src/packages/database/pool/util.ts +++ b/src/packages/database/pool/util.ts @@ -1,5 +1,14 @@ +/* + * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. + * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details + */ + +// Postgres 14 is different than 13 an earlier. +// extract(epoch from timestamp) returns a "numeric", which is converted to a string by the pg driver. +// we convert this explicitly to a floating point number to get the ms since epoch. +// Note: JavaScript's new Date(...) has no hesitation converting from a float. export function timeInSeconds(field: string, asField?: string): string { - return ` EXTRACT(EPOCH FROM ${field})*1000 as ${asField ?? field} `; + return ` (EXTRACT(EPOCH FROM ${field})*1000)::FLOAT as ${asField ?? field} `; } // Given number of seconds **in the future**. diff --git a/src/packages/database/postgres-blobs.coffee b/src/packages/database/postgres-blobs.coffee index 97477ca4ad..198f2432a7 100644 --- a/src/packages/database/postgres-blobs.coffee +++ b/src/packages/database/postgres-blobs.coffee @@ -804,20 +804,12 @@ exports.extend_PostgreSQL = (ext) -> class PostgreSQL extends ext string_id : required cb : required # cb(err, array) @_query - query : "SELECT extract(epoch from time)*1000 as epoch, * FROM patches" + query : "SELECT * FROM patches" where : {"string_id = $::CHAR(40)" : opts.string_id} cb : all_results (err, patches) => if err opts.cb(err) else - for p in patches - # TODO why using epoch and then converting to Date, why not just taking time? - # Besides that: @hsy noticed in development that p.epoch could be a string, resulting in an invalid date. - if typeof p.epoch == 'string' - p.time = new Date(parseInt(p.epoch)) - else - p.time = new Date(p.epoch) - delete p.epoch opts.cb(undefined, patches) import_patches: (opts) => diff --git a/src/packages/next/pages/share/public_paths/page/[page].tsx b/src/packages/next/pages/share/public_paths/page/[page].tsx index 7596b1a925..7dd41c9a5f 100644 --- a/src/packages/next/pages/share/public_paths/page/[page].tsx +++ b/src/packages/next/pages/share/public_paths/page/[page].tsx @@ -11,7 +11,7 @@ such as Google, and only exists for that purpose. import Link from "next/link"; import SiteName from "components/share/site-name"; -import getPool from "@cocalc/database/pool"; +import getPool, { timeInSeconds } from "@cocalc/database/pool"; import PublicPaths from "components/share/public-paths"; import { Layout } from "components/share/layout"; import withCustomize from "lib/with-customize"; @@ -85,7 +85,7 @@ export async function getServerSideProps(context) { const page = getPage(context.params); const pool = getPool("medium"); const { rows } = await pool.query( - `SELECT id, path, description, EXTRACT(EPOCH FROM last_edited)*1000 AS last_edited + `SELECT id, path, description, ${timeInSeconds("last_edited")} FROM public_paths WHERE vhost IS NULL AND disabled IS NOT TRUE AND unlisted IS NOT TRUE AND ((authenticated IS TRUE AND $1 IS TRUE) OR (authenticated IS NOT TRUE)) diff --git a/src/packages/server/projects/control/stop-idle-projects.ts b/src/packages/server/projects/control/stop-idle-projects.ts index 62a0e4ef8e..f6ffb259f5 100644 --- a/src/packages/server/projects/control/stop-idle-projects.ts +++ b/src/packages/server/projects/control/stop-idle-projects.ts @@ -20,9 +20,10 @@ async function stopIdleProjects(stopProject: (string) => Promise) { logger.debug("query database for all running projects"); const runningProjects = ( + // ::float necessary for Postgres 14, see @cocalc/database/pool/util.ts timeInSeconds for more info await callback2(db()._query, { query: - "SELECT project_id, EXTRACT(EPOCH FROM NOW() - last_edited) as idle_time, settings, run_quota FROM projects WHERE state ->> 'state' = 'running'", + "SELECT project_id, (EXTRACT(EPOCH FROM NOW() - last_edited))::FLOAT as idle_time, settings, run_quota FROM projects WHERE state ->> 'state' = 'running'", }) ).rows; logger.debug("got ", runningProjects); From 95f22ec5e676e9a2ba84a7a6d74271d2cfe912e7 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Mon, 4 Apr 2022 11:06:32 +0200 Subject: [PATCH 2/2] server/stop-idle-projects: just linebreaks to make query more readable --- src/packages/server/projects/control/stop-idle-projects.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/packages/server/projects/control/stop-idle-projects.ts b/src/packages/server/projects/control/stop-idle-projects.ts index f6ffb259f5..68188f5f1f 100644 --- a/src/packages/server/projects/control/stop-idle-projects.ts +++ b/src/packages/server/projects/control/stop-idle-projects.ts @@ -20,10 +20,11 @@ async function stopIdleProjects(stopProject: (string) => Promise) { logger.debug("query database for all running projects"); const runningProjects = ( - // ::float necessary for Postgres 14, see @cocalc/database/pool/util.ts timeInSeconds for more info await callback2(db()._query, { - query: - "SELECT project_id, (EXTRACT(EPOCH FROM NOW() - last_edited))::FLOAT as idle_time, settings, run_quota FROM projects WHERE state ->> 'state' = 'running'", + // ::float necessary for Postgres 14, see @cocalc/database/pool/util.ts timeInSeconds for more info + query: `SELECT project_id, (EXTRACT(EPOCH FROM NOW() - last_edited))::FLOAT as idle_time, settings, run_quota + FROM projects + WHERE state ->> 'state' = 'running'`, }) ).rows; logger.debug("got ", runningProjects);