From f212f33baf8193a7e94979430483ebbbe64a8be2 Mon Sep 17 00:00:00 2001 From: kaby76 Date: Fri, 19 Jun 2026 07:19:30 -0400 Subject: [PATCH 1/2] Add weekly DBLP update for GQL concept page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - code/updateDblpGql.js: fetches DBLP publications for GQL (ISO 39075) and SQL/PGQ from the last 5 years and writes them into the `dblp` block of concepts/graph-query-language.scroll - build.js: calls updateDblpGql as Step 2.7, so it runs on every build (both the weekly Monday schedule and normal push/PR builds) - concepts/graph-query-language.scroll: initial dblp block with 14 publications (2021–2026) from two DBLP queries: "GQL graph" and "SQL/PGQ" Co-Authored-By: Claude Sonnet 4.6 --- build.js | 9 ++ code/updateDblpGql.js | 162 +++++++++++++++++++++++++++ concepts/graph-query-language.scroll | 19 ++++ 3 files changed, 190 insertions(+) create mode 100644 code/updateDblpGql.js diff --git a/build.js b/build.js index 6dd221087f2d..f758f3bc1a01 100644 --- a/build.js +++ b/build.js @@ -175,6 +175,15 @@ async function build() { console.warn(`⚠️ Could not fetch latest news (${err.message}), keeping existing latestNews.scroll`) } + // Step 2.7: Update DBLP publications for GQL concept page + console.log('\nπŸ“‹ Step 2.7: Updating DBLP GQL publications...') + try { + const { updateDblpGql } = require('./code/updateDblpGql.js') + await updateDblpGql() + } catch (err) { + console.warn(`⚠️ Could not update DBLP GQL data (${err.message}), keeping existing data`) + } + // Step 3: Build root folder (generates pldb.json, measures.json) console.log('\nπŸ“‹ Step 3: Building root folder...') run(`node "${SCROLL_CLI}" build`) diff --git a/code/updateDblpGql.js b/code/updateDblpGql.js new file mode 100644 index 000000000000..a64c6d628eb0 --- /dev/null +++ b/code/updateDblpGql.js @@ -0,0 +1,162 @@ +'use strict' + +/** + * Fetches recent DBLP publications about GQL (Graph Query Language / ISO 39075) + * and updates concepts/graph-query-language.scroll with a `dblp` section. + * + * Searches run against the public DBLP JSON API (no key required). + * Results are filtered to the most recent YEARS years and deduplicated. + */ + +const https = require('https') +const fs = require('fs') +const path = require('path') + +const ROOT = path.join(__dirname, '..') +const SCROLL_FILE = path.join(ROOT, 'concepts', 'graph-query-language.scroll') +const DBLP_SEARCH_URL = 'https://dblp.org/search?q=GQL+graph+query+language' +const YEARS = 5 + +// DBLP search queries whose hits are merged and deduplicated. +// "GQL graph" matches papers about the ISO GQL standard. +// "SQL/PGQ" matches the companion SQL Property Graph Query standard. +const QUERIES = [ + 'GQL graph', + 'SQL/PGQ' +] + +// ─── HTTP helpers ───────────────────────────────────────────────────────────── + +function fetchUrl(url, timeoutMs = 15000) { + return new Promise((resolve, reject) => { + const req = https.get(url, { headers: { 'User-Agent': 'PLDB-DblpGql/1.0 (+https://pldb.info)' } }, res => { + if (res.statusCode < 200 || res.statusCode >= 300) { + res.resume() + return reject(new Error(`HTTP ${res.statusCode}`)) + } + let data = '' + res.setEncoding('utf8') + res.on('data', chunk => { data += chunk }) + res.on('end', () => resolve(data)) + res.on('error', reject) + }) + req.on('error', reject) + req.setTimeout(timeoutMs, () => req.destroy(new Error('DBLP fetch timed out'))) + }) +} + +function dblpApiUrl(query) { + return `https://dblp.org/search/publ/api?q=${encodeURIComponent(query)}&format=json&h=100&f=0` +} + +// ─── DBLP parsing ───────────────────────────────────────────────────────────── + +function decodeEntities(s) { + return String(s || '') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'|'/g, "'") + .replace(/&#(\d+);/g, (_, n) => String.fromCharCode(Number(n))) +} + +function getAuthors(info) { + const rawAuthors = info.authors?.author + const list = Array.isArray(rawAuthors) + ? rawAuthors + : rawAuthors && typeof rawAuthors === 'object' + ? [rawAuthors] + : [] + return list + .filter(a => a && typeof a.text === 'string') + .map(a => a.text) + .join(' and ') +} + +function safePsvCell(value) { + return String(value || '').replace(/\|/g, '-').replace(/\n/g, ' ').trim() +} + +async function fetchDblpQuery(query) { + const raw = await fetchUrl(dblpApiUrl(query)) + const data = JSON.parse(raw) + return data.result?.hits?.hit || [] +} + +// ─── Main export ────────────────────────────────────────────────────────────── + +async function updateDblpGql() { + const minYear = new Date().getFullYear() - YEARS + const seen = new Set() + const pubs = [] + + for (const q of QUERIES) { + let hits + try { + hits = await fetchDblpQuery(q) + } catch (err) { + console.warn(` ⚠️ DBLP query "${q}" failed: ${err.message}`) + continue + } + for (const h of hits) { + const info = h.info || {} + const year = parseInt(info.year || 0, 10) + if (year < minYear) continue + const key = info.key || info.url || '' + if (!key || seen.has(key)) continue + seen.add(key) + + const eeRaw = info.ee + const url = Array.isArray(eeRaw) ? eeRaw[0] : (eeRaw || info.url || '') + + pubs.push({ + year, + title: decodeEntities((info.title || '').replace(/\.$/, '')), + doi: info.doi || '', + url, + authors: getAuthors(info) + }) + } + } + + // Sort by year descending, then title ascending + pubs.sort((a, b) => b.year - a.year || a.title.localeCompare(b.title)) + + const hitCount = pubs.length + + // Build PSV table (indented under `publications`) + const psvHeader = 'year|title|doi|url' + const psvRows = pubs.map(p => + [p.year, safePsvCell(p.title), safePsvCell(p.doi), safePsvCell(p.url)].join('|') + ) + const psvLines = [psvHeader, ...psvRows] + + // Build the dblp block (Scroll indentation: 1 space for children, 2 for grandchildren) + const dblpBlock = + `dblp ${DBLP_SEARCH_URL}\n` + + ` hits ${hitCount}\n` + + ` publications\n` + + psvLines.map(line => ` ${line}`).join('\n') + + '\n' + + // Read scroll file and replace or append the dblp block + let content = fs.readFileSync(SCROLL_FILE, 'utf8') + + // Match from "dblp " through its indented children (lines starting with space/tab) + const dblpBlockRe = /^dblp [^\n]*\n(?:[ \t][^\n]*\n?)*/m + if (dblpBlockRe.test(content)) { + content = content.replace(dblpBlockRe, dblpBlock) + } else { + content = content.trimEnd() + '\n\n' + dblpBlock + } + + fs.writeFileSync(SCROLL_FILE, content) + console.log(` βœ… DBLP GQL: ${hitCount} publications (${minYear}–present) written to graph-query-language.scroll`) +} + +module.exports = { updateDblpGql } + +if (require.main === module) { + updateDblpGql().catch(err => { console.error(err); process.exit(1) }) +} diff --git a/concepts/graph-query-language.scroll b/concepts/graph-query-language.scroll index b10dc14730fd..e81a9459d8f5 100644 --- a/concepts/graph-query-language.scroll +++ b/concepts/graph-query-language.scroll @@ -52,3 +52,22 @@ wikipedia https://en.wikipedia.org/wiki/Graph_Query_Language backlinksCount 500 revisionCount 190 appeared 2024 + +dblp https://dblp.org/search?q=GQL+graph+query+language + hits 14 + publications + year|title|doi|url + 2026|GQL Rules: Deterministic and Safe Rule-Based Materialization for Property Graphs|10.1109/ACCESS.2026.3686122|https://doi.org/10.1109/ACCESS.2026.3686122 + 2025|GQL and SQL/PGQ: Theoretical Models and Expressive Power|10.14778/3725688.3725707|https://www.vldb.org/pvldb/vol18/p1798-libkin.pdf + 2025|Mine Graph Rule: A New GQL Operator for Mining Association Rules in Property Graph Databases|10.1007/S00778-025-00934-8|https://doi.org/10.1007/s00778-025-00934-8 + 2025|Parallel and Distributed SQL/PGQ Query Processing for Property Graphs|10.1007/978-3-032-02215-8_12|https://doi.org/10.1007/978-3-032-02215-8_12 + 2025|Towards Cross-Model Efficiency in SQL/PGQ|10.1145/3735546.3735860|https://doi.org/10.1145/3735546.3735860 + 2025|Towards Cross-Model Efficiency in SQL/PGQ|10.48550/ARXIV.2505.07595|https://doi.org/10.48550/arXiv.2505.07595 + 2024|Chimera: A system design of dual storage and traversal-join unified query processing for SQL/PGQ|10.14778/3705829.3705845|https://www.vldb.org/pvldb/vol18/p279-lee.pdf + 2024|GQL and SQL/PGQ: Theoretical Models and Expressive Power|10.48550/ARXIV.2409.01102|https://doi.org/10.48550/arXiv.2409.01102 + 2023|DuckPGQ: Bringing SQL/PGQ to DuckDB|10.14778/3611540.3611614|https://www.vldb.org/pvldb/vol16/p4034-wolde.pdf + 2023|Evaluating Regular Path Queries in GQL and SQL/PGQ: How Far Can The Classical Algorithms Take Us?|10.48550/ARXIV.2306.02194|https://doi.org/10.48550/arXiv.2306.02194 + 2023|SQL/PGQ: A Systems Perspective (Abstract)||https://ceur-ws.org/Vol-3409/keynote04.pdf + 2022|Graph Pattern Matching in GQL and SQL/PGQ|10.1145/3514221.3526057|https://doi.org/10.1145/3514221.3526057 + 2022|Graph Query Language (GQL)-structured Algorithms for Geospatial Intelligence on Public Transportation|10.1109/ICTC55196.2022.9952570|https://doi.org/10.1109/ICTC55196.2022.9952570 + 2021|Graph Pattern Matching in GQL and SQL/PGQ||https://arxiv.org/abs/2112.06217 From a11e000501a1d70c27c0080ba33490f24c08621f Mon Sep 17 00:00:00 2001 From: kaby76 Date: Fri, 19 Jun 2026 07:29:33 -0400 Subject: [PATCH 2/2] Move DBLP GQL update to a separate Tuesday workflow - Remove updateDblpGql call from build.js (no longer runs on every build) - Add .github/workflows/update-dblp-gql.yml: runs every Tuesday at 06:00 UTC, fetches fresh DBLP data, commits the updated scroll, and pushes (which then triggers the Monday build.yml on the next push to main) - Also supports manual dispatch via workflow_dispatch Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/update-dblp-gql.yml | 32 +++++++++++++++++++++++++++ build.js | 9 -------- 2 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/update-dblp-gql.yml diff --git a/.github/workflows/update-dblp-gql.yml b/.github/workflows/update-dblp-gql.yml new file mode 100644 index 000000000000..c30e42a84568 --- /dev/null +++ b/.github/workflows/update-dblp-gql.yml @@ -0,0 +1,32 @@ +name: Update DBLP GQL Publications + +on: + schedule: + - cron: '0 6 * * 2' # Every Tuesday at 06:00 UTC + workflow_dispatch: # Allow manual runs + +jobs: + update-dblp-gql: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Update DBLP GQL publications + run: node code/updateDblpGql.js + + - name: Commit updated scroll file + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add concepts/graph-query-language.scroll + git diff --cached --quiet || git commit -m "Update DBLP GQL publications (weekly)" + git push diff --git a/build.js b/build.js index f758f3bc1a01..6dd221087f2d 100644 --- a/build.js +++ b/build.js @@ -175,15 +175,6 @@ async function build() { console.warn(`⚠️ Could not fetch latest news (${err.message}), keeping existing latestNews.scroll`) } - // Step 2.7: Update DBLP publications for GQL concept page - console.log('\nπŸ“‹ Step 2.7: Updating DBLP GQL publications...') - try { - const { updateDblpGql } = require('./code/updateDblpGql.js') - await updateDblpGql() - } catch (err) { - console.warn(`⚠️ Could not update DBLP GQL data (${err.message}), keeping existing data`) - } - // Step 3: Build root folder (generates pldb.json, measures.json) console.log('\nπŸ“‹ Step 3: Building root folder...') run(`node "${SCROLL_CLI}" build`)