NodeBB/NodeBB

View on GitHub
src/database/postgres/list.js

Summary

Maintainability
C
1 day
Test Coverage
'use strict';

module.exports = function (module) {
    const helpers = require('./helpers');

    module.listPrepend = async function (key, value) {
        if (!key) {
            return;
        }

        await module.transaction(async (client) => {
            async function doPrepend(value) {
                await client.query({
                    name: 'listPrepend',
                    text: `
    INSERT INTO "legacy_list" ("_key", "array")
    VALUES ($1::TEXT, ARRAY[$2::TEXT])
    ON CONFLICT ("_key")
    DO UPDATE SET "array" = ARRAY[$2::TEXT] || "legacy_list"."array"`,
                    values: [key, value],
                });
            }

            await helpers.ensureLegacyObjectType(client, key, 'list');
            if (Array.isArray(value)) {
                // TODO: perf make single query
                for (const v of value) {
                    // eslint-disable-next-line
                    await doPrepend(v);
                }
                return;
            }
            await doPrepend(value);
        });
    };

    module.listAppend = async function (key, value) {
        if (!key) {
            return;
        }
        await module.transaction(async (client) => {
            async function doAppend(value) {
                await client.query({
                    name: 'listAppend',
                    text: `
    INSERT INTO "legacy_list" ("_key", "array")
    VALUES ($1::TEXT, ARRAY[$2::TEXT])
    ON CONFLICT ("_key")
    DO UPDATE SET "array" = "legacy_list"."array" || ARRAY[$2::TEXT]`,
                    values: [key, value],
                });
            }
            await helpers.ensureLegacyObjectType(client, key, 'list');
            if (Array.isArray(value)) {
                // TODO: perf make single query
                for (const v of value) {
                    // eslint-disable-next-line
                    await doAppend(v);
                }
                return;
            }
            await doAppend(value);
        });
    };

    module.listRemoveLast = async function (key) {
        if (!key) {
            return;
        }

        const res = await module.pool.query({
            name: 'listRemoveLast',
            text: `
WITH A AS (
    SELECT l.*
      FROM "legacy_object_live" o
     INNER JOIN "legacy_list" l
             ON o."_key" = l."_key"
            AND o."type" = l."type"
     WHERE o."_key" = $1::TEXT
       FOR UPDATE)
UPDATE "legacy_list" l
   SET "array" = A."array"[1 : array_length(A."array", 1) - 1]
  FROM A
 WHERE A."_key" = l."_key"
RETURNING A."array"[array_length(A."array", 1)] v`,
            values: [key],
        });

        return res.rows.length ? res.rows[0].v : null;
    };

    module.listRemoveAll = async function (key, value) {
        if (!key) {
            return;
        }

        await module.pool.query({
            name: 'listRemoveAll',
            text: `
UPDATE "legacy_list" l
   SET "array" = array_remove(l."array", $2::TEXT)
  FROM "legacy_object_live" o
 WHERE o."_key" = l."_key"
   AND o."type" = l."type"
   AND o."_key" = $1::TEXT`,
            values: [key, value],
        });
    };

    module.listTrim = async function (key, start, stop) {
        if (!key) {
            return;
        }

        stop += 1;

        await module.pool.query(stop > 0 ? {
            name: 'listTrim',
            text: `
UPDATE "legacy_list" l
   SET "array" = ARRAY(SELECT m.m
                         FROM UNNEST(l."array") WITH ORDINALITY m(m, i)
                        ORDER BY m.i ASC
                        LIMIT ($3::INTEGER - $2::INTEGER)
                       OFFSET $2::INTEGER)
  FROM "legacy_object_live" o
 WHERE o."_key" = l."_key"
   AND o."type" = l."type"
   AND o."_key" = $1::TEXT`,
            values: [key, start, stop],
        } : {
            name: 'listTrimBack',
            text: `
UPDATE "legacy_list" l
   SET "array" = ARRAY(SELECT m.m
                         FROM UNNEST(l."array") WITH ORDINALITY m(m, i)
                        ORDER BY m.i ASC
                        LIMIT ($3::INTEGER - $2::INTEGER + array_length(l."array", 1))
                       OFFSET $2::INTEGER)
  FROM "legacy_object_live" o
 WHERE o."_key" = l."_key"
   AND o."type" = l."type"
   AND o."_key" = $1::TEXT`,
            values: [key, start, stop],
        });
    };

    module.getListRange = async function (key, start, stop) {
        if (!key) {
            return;
        }

        stop += 1;

        const res = await module.pool.query(stop > 0 ? {
            name: 'getListRange',
            text: `
SELECT ARRAY(SELECT m.m
               FROM UNNEST(l."array") WITH ORDINALITY m(m, i)
              ORDER BY m.i ASC
              LIMIT ($3::INTEGER - $2::INTEGER)
             OFFSET $2::INTEGER) l
  FROM "legacy_object_live" o
 INNER JOIN "legacy_list" l
         ON o."_key" = l."_key"
        AND o."type" = l."type"
      WHERE o."_key" = $1::TEXT`,
            values: [key, start, stop],
        } : {
            name: 'getListRangeBack',
            text: `
SELECT ARRAY(SELECT m.m
               FROM UNNEST(l."array") WITH ORDINALITY m(m, i)
              ORDER BY m.i ASC
              LIMIT ($3::INTEGER - $2::INTEGER + array_length(l."array", 1))
             OFFSET $2::INTEGER) l
  FROM "legacy_object_live" o
 INNER JOIN "legacy_list" l
         ON o."_key" = l."_key"
        AND o."type" = l."type"
 WHERE o."_key" = $1::TEXT`,
            values: [key, start, stop],
        });

        return res.rows.length ? res.rows[0].l : [];
    };

    module.listLength = async function (key) {
        const res = await module.pool.query({
            name: 'listLength',
            text: `
SELECT array_length(l."array", 1) l
  FROM "legacy_object_live" o
 INNER JOIN "legacy_list" l
         ON o."_key" = l."_key"
        AND o."type" = l."type"
      WHERE o."_key" = $1::TEXT`,
            values: [key],
        });

        return res.rows.length ? res.rows[0].l : 0;
    };
};