)]}'
{
  "log": [
    {
      "commit": "cfd3b634ab3b1ec74ddcd153a18d98113a4f9df6",
      "tree": "c8081684d90cd771be3b3a43ca66af900234a8c0",
      "parents": [
        "801417404978823bd8732452c3f7959017584785"
      ],
      "author": {
        "name": "Eeshwar Gurushankar",
        "email": "eeshwarg13@gmail.com",
        "time": "Fri Aug 14 17:24:31 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 14 17:24:31 2026 -0700"
      },
      "message": "Fix drop_label privilege check for non-superusers (unrecognized class ID) (#2497)\n\n* Fix drop_label privilege check for non-superusers\n\nrange_var_callback_for_remove_relation() called object_ownercheck() with\nthe wrong argument order for the PG16+ API\nobject_ownercheck(classid, objectid, roleid). It passed rel_oid as the\nclassid and a namespace OID as the objectid, so the classid lookup hit\nthe default case and raised \"unrecognized class ID\" for any\nnon-superuser dropping a graph label. Superusers were unaffected because\nobject_ownercheck() early-returns via superuser_arg().\n\nPass RelationRelationId as the classid and rel_oid as the objectid,\nmatching upstream PostgreSQL\u0027s RangeVarCallbackForDropRelation.\n\nAdd a regression test in security.sql covering a NOSUPERUSER role that\nowns a graph/label and successfully runs drop_label.\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n* Make drop_label regression test output db-name independent\n\nReplace the SELECT ... \\gexec dynamic GRANT with a DO block that runs\nEXECUTE format(...) internally. The \\gexec approach echoed the expanded\nGRANT (hardcoding the regression database name) into the expected output,\nmaking the test brittle across harnesses. The DO block grants on\ncurrent_database() while emitting a deterministic \u0027DO\u0027.\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n---------\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e"
    },
    {
      "commit": "801417404978823bd8732452c3f7959017584785",
      "tree": "3632f89226c2ad34708224002f08b8aeff89335c",
      "parents": [
        "6876abcab0a3281eb65a7e2a91238e0b5abfdea7"
      ],
      "author": {
        "name": "mmustafasenoglu",
        "email": "mmustafasenoglu0@gmail.com",
        "time": "Fri Jul 17 12:42:01 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 17 14:42:01 2026 +0500"
      },
      "message": "Add delimiter parameter to load_labels_from_file and load_edges_from_file (#2469)\n\nBoth load_labels_from_file and load_edges_from_file relied on PostgreSQL\nCOPY\u0027s default comma delimiter, causing silent data corruption on labels\nand segfault on edges when using non-comma-delimited files.\n\nThis adds an optional \u0027delimiter\u0027 TEXT parameter (default \u0027,\u0027) to both\nfunctions, allowing users to specify the field separator:\n\n  SELECT load_labels_from_file(\u0027graph\u0027, \u0027Label\u0027, \u0027file.csv\u0027, true, false, \u0027|\u0027);\n  SELECT load_edges_from_file(\u0027graph\u0027, \u0027Edge\u0027, \u0027file.csv\u0027, false, \u0027|\u0027);\n\nChanges:\n- create_copy_options() now accepts and passes the delimiter to COPY\n- SQL function signatures updated with new delimiter parameter\n- Regression tests added for pipe-delimited vertex and edge files\n\nAdd DROP/CREATE for load_labels_from_file and load_edges_from_file\nwith the new delimiter parameter to age--1.8.0--y.y.y.sql. Without\nthese definitions, the age_upgrade regression test fails because\nthe old function signatures remain after upgrade.\n\nSigned-off-by: Mustafa Senoglu \u003cmmustafasenoglu0@gmail.com\u003e"
    },
    {
      "commit": "6876abcab0a3281eb65a7e2a91238e0b5abfdea7",
      "tree": "8ad3daf0e65be0530ac5d2e428b1413f116628b5",
      "parents": [
        "2e4b4ad91359b6e4dedbe787b83176f565bf7727"
      ],
      "author": {
        "name": "mmustafasenoglu",
        "email": "mmustafasenoglu0@gmail.com",
        "time": "Wed Jul 15 11:30:37 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jul 15 13:30:37 2026 +0500"
      },
      "message": "Fix misleading text in README Pre-Installation section (#2470)\n\nRemove \u0027(Ubuntu package names shown below)\u0027 since the section\nlists packages for CentOS, Fedora, and Ubuntu - not just Ubuntu.\n\nFixes #403"
    },
    {
      "commit": "2e4b4ad91359b6e4dedbe787b83176f565bf7727",
      "tree": "aa4ca5ca442fe748a5a783cd031646de0b41ce34",
      "parents": [
        "22a1dc976273a6d03f0af55398ba0ca92078c2d3"
      ],
      "author": {
        "name": "Ben",
        "email": "52712273+BenSpex@users.noreply.github.com",
        "time": "Wed Jul 15 10:29:28 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jul 15 13:29:28 2026 +0500"
      },
      "message": "Fix segfault on DETACH DELETE under RLS with a function-based edge policy (#2475)\n\nA non-superuser subject to row-level security crashed the backend with\nSIGSEGV when running DETACH DELETE on a vertex that has a connected edge,\nif the edge label carried an RLS policy whose USING/WITH CHECK qual\ninvokes a function (e.g. a STABLE tenant/owner accessor). Reported in\nissue #2474; reproduces on 1.7.0 and master.\n\nMechanism:\n\nAGE deletes a vertex\u0027s connected edges in check_for_connected_edges(),\nwhich is called from end_cypher_delete() -- i.e. during executor shutdown\n(ExecEndPlan, reached via PortalCleanup -\u003e ExecutorEnd when the portal is\ndropped). By that point the portal\u0027s active snapshot has already been\npopped, so the active-snapshot stack is empty.\n\nRLS for Cypher DELETE is enforced at the executor level (added in #2309):\ncheck_for_connected_edges() compiles the edge label\u0027s security quals and\nevaluates them per candidate edge with check_security_quals() -\u003e ExecQual().\nWhen the qual calls a SQL-language function, ExecQual() dispatches into\nfmgr_sql() -\u003e postquel_start(), which runs the function\u0027s query and reads\nthe current snapshot with GetActiveSnapshot(). With no snapshot on the\nstack that is a NULL-pointer dereference -\u003e SIGSEGV (an assert build trips\nthe Assert(ActiveSnapshotSet()) in postquel_start first).\n\nThis is why the narrowing in the report holds: the vertex path\n(process_delete_list) runs during normal execution while a snapshot is\nactive; edge-only DELETE and node-only DETACH DELETE never evaluate an\nedge qual at teardown; a superuser bypasses RLS; and a constant-only edge\npolicy never enters fmgr_sql, so only DETACH DELETE of an edge-connected\nvertex under a function-bearing edge policy trips it.\n\nFix:\n\nEnsure an active snapshot for the duration of the connected-edge scan in\ncheck_for_connected_edges(). es_snapshot is still valid there (the EState\nis not torn down until this returns) and is the correct snapshot for\nreading the edges, so push it if none is active and pop it before return.\nThe scans themselves already pass es_snapshot explicitly, so this only\naffects RLS qual evaluation; non-RLS paths are unchanged. On the error\npath (e.g. an RLS denial raised mid-scan) transaction abort resets the\nactive-snapshot stack, so the unpaired push is cleaned up.\n\nTest:\n\nAdds a regression to the security suite (PART 11b): an edge label policy\nwhose qual calls a STABLE SQL function, then DETACH DELETE of an\nedge-connected vertex as the RLS-bound role. It must delete the vertex and\nits edge (leaving the other endpoint) instead of crashing. The existing\nPART 11 uses a constant-only edge policy, which does not exercise the\nsnapshot-dependent function path."
    },
    {
      "commit": "22a1dc976273a6d03f0af55398ba0ca92078c2d3",
      "tree": "f24725014c11e172b9738b713b3d4651c6322a35",
      "parents": [
        "9fb7df8c668f0ffb5607f8ecf99953fc5cebfb48"
      ],
      "author": {
        "name": "Thomas Kennedy",
        "email": "61436056+wilmort@users.noreply.github.com",
        "time": "Wed Jul 15 04:27:19 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jul 15 13:27:19 2026 +0500"
      },
      "message": "docs: update dead links to age.apache.org/docs and java2s.com/ (#2476)\n\n1.  drivers/golang/README.md \n\t-  https://age.apache.org/docs/ -\u003e 404\n\t- Changed to https://age.apache.org/age-manual/master/index.html\n\n2. drivers/golang/TYPES.md\n\t- https://age.apache.org/docs/Apache_AGE_Guide.pdf -\u003e 404\n\t- Changed to https://age.apache.org/age-manual/master/intro/types.html\n\t\n3. drivers/jdbc/README.md \n\t-  http://www.java2s.com/Code/Jar/c/Downloadcommonlang3jar.htm -\u003e wordpress test page\n\t- changed to https://mvnrepository.com/artifact/org.apache.commons/commons-lang3\n\t\n\t- http://www.java2s.com/ref/jar/download-commonstext16jar-file.html -\u003e wordpress test page\n\t- changed to https://mvnrepository.com/artifact/org.apache.commons/commons-text"
    },
    {
      "commit": "9fb7df8c668f0ffb5607f8ecf99953fc5cebfb48",
      "tree": "8e7e45fd8484094e2279a25d50043ce157323c3c",
      "parents": [
        "fef547000e84ce73d44bbff81aa67e74e316858c"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Jul 03 09:37:05 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 03 21:37:05 2026 +0500"
      },
      "message": "Advance master branch to Apache AGE version 1.8.0 (#2455)\n\nUpdated the following files to advance the Apache AGE version\nto 1.8.0\n\nmodified:   META.json\nmodified:   README.md\nmodified:   RELEASE\nrenamed:    age--1.7.0--y.y.y.sql -\u003e age--1.7.0--1.8.0.sql\nmodified:   age.control\nmodified:   docker/Dockerfile\nnew file:   age--1.8.0--y.y.y.sql"
    },
    {
      "commit": "fef547000e84ce73d44bbff81aa67e74e316858c",
      "tree": "8ea2327301ccb0f0af6a16f92bc05011f7f383a8",
      "parents": [
        "53ecacf81c7a4d8620a3502e8153193520afcbc8"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Jul 03 08:23:05 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 03 20:23:05 2026 +0500"
      },
      "message": "Fix installcheck fetch-depth parameter to 100 (#2461)\n\nFix the installcheck.yaml fetch-depth parameter to 100. This release,\nbeing rather large, exceeded the original value of 75 (commits) and\ncaused the upgrade test to fail. The new value will resolve this and\nmake it unlikely to become a problem in the future.\n\nNOTE: We need to keep releases under this number of commits.\n\nmodified:   .github/workflows/installcheck.yaml"
    },
    {
      "commit": "53ecacf81c7a4d8620a3502e8153193520afcbc8",
      "tree": "cb394df8d787ca7eafbbb4177ce64d5003bd91b6",
      "parents": [
        "39273ca8f6687d6cf0e723045ae1407c879f263f"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Fri Jul 03 08:27:23 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 03 17:27:23 2026 +0500"
      },
      "message": "Fix segfault on CREATE/MERGE/SET with generated or extra label columns (#2458)\n\nAGE builds the insert/update tuple slot from the full label-table\ndescriptor (table_slot_create), but only populates the columns it\nmanages: id/properties for vertices and id/start_id/end_id/properties\nfor edges. A user-added column on the label table -- most notably a\nGENERATED ALWAYS ... STORED column, but also any plain ADD COLUMN --\ntherefore left an uninitialized slot entry. ExecStoreVirtualTuple marks\nall attributes valid, so heap_form_tuple() -\u003e heap_compute_data_size()\nread the uninitialized tts_isnull[]/tts_values[] for that column and\nsegfaulted dereferencing garbage as a varlena. This crashed the backend\non CREATE, MERGE and SET.\n\nFix in two parts:\n\n- clear_entity_slot() clears the slot and marks every attribute NULL\n  before AGE fills in its own columns, so columns AGE does not manage\n  default to NULL instead of stale slot memory. It is used at every slot\n  build site: create_vertex, create_edge, merge_vertex, merge_edge, and\n  the populate_vertex_tts/populate_edge_tts helpers used by SET.\n\n- insert_entity_tuple_cid (CREATE/MERGE) and update_entity_tuple (SET)\n  call ExecComputeStoredGenerated() before materializing the heap tuple\n  when the relation has stored generated columns, so those columns are\n  computed on insert and recomputed on update per PostgreSQL semantics.\n\nAdds a generated_columns regression test covering stored generated\ncolumns on vertex and edge labels across CREATE, MERGE and SET, plus a\nplain user column that must not crash."
    },
    {
      "commit": "39273ca8f6687d6cf0e723045ae1407c879f263f",
      "tree": "cb0a7f5fa8faade533df0e89741907e1ac3a74bf",
      "parents": [
        "d84b4578fe7551211d8bf5fe8d6adf3bcc086e78"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Fri Jul 03 16:20:59 2026 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 03 07:20:59 2026 -0400"
      },
      "message": "Propagate null through unnest and single() three-valued logic (#2406)\n\nFix age_unnest to emit SQL NULL for AGTV_NULL elements and rewrite single() to proper three-valued logic, fixing UNWIND [null] count, list-comprehension null filters, and single() semantics per openCypher.\n\nCloses #2383\nCloses #2393"
    },
    {
      "commit": "d84b4578fe7551211d8bf5fe8d6adf3bcc086e78",
      "tree": "b58dc55aa23da67a5fcc384776bc4f243da08674",
      "parents": [
        "c1617a2628eedbfe10c3332791bc7dde2f3cba44"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Thu Jul 02 10:23:39 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 02 13:23:39 2026 -0400"
      },
      "message": "Add automatic header-dependency tracking to the Makefile (#2454)\n\nAGE lists OBJS explicitly and relies on PGXS, whose built-in dependency\ntracking only runs when the server was built with --enable-depend (often\ndisabled). Consequently, editing a header did not recompile the .c files\nthat include it, leaving stale .o files. This is especially dangerous for\nnode/struct headers: a stale ag_nodes.o keeps an outdated node_size, so\n_readExtensibleNode under-allocates and readNode corrupts the heap\n(\"unrecognized node type\").\n\nEmit a .d file beside each object via -MMD -MP and -include them, deriving\nDEPFILES from OBJS. The mechanism is self-contained (independent of\n--enable-depend): -MMD skips system headers and -MP tolerates deleted\nheaders. On servers built with --enable-depend, PGXS appends its own -MF\nafter CFLAGS (last -MF wins), so this degrades cleanly to PGXS\u0027s tracking.\nAdd DEPFILES to EXTRA_CLEAN and *.d to .gitignore.\n\nCo-authored-by: Copilot \u003ccopilot@github.com\u003e\n\nmodified:   .gitignore\nmodified:   Makefile"
    },
    {
      "commit": "c1617a2628eedbfe10c3332791bc7dde2f3cba44",
      "tree": "2f52ba9a562efe6655cfa822e50cae4288ed487f",
      "parents": [
        "09fce2ced3e93bd76e237211001ad332e44c4e8b"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Thu Jul 02 13:02:07 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 02 10:02:07 2026 -0700"
      },
      "message": "ci: pin Build/Regression runner to ubuntu-24.04 and guard Bison version for GLR grammar (#2445)\n\n* ci: pin runner to ubuntu-24.04 + guard Bison version for GLR grammar\n\nThe Cypher GLR grammar pins exact shift/reduce and reduce/reduce conflict\ncounts via %expect / %expect-rr in cypher_gram.y, and Bison treats %expect\nas exact-match: a different Bison version can report different counts and\nbreak the build. ubuntu-latest floats to new Ubuntu releases (and new Bison\nversions), which would silently shift those counts.\n\nPin runs-on to ubuntu-24.04 to freeze Bison at 3.8.x, and add a guard step\nthat fails loudly with a pointer to cypher_gram.y if Bison ever drifts off\n3.8.x. Reproducibility comes from pinning the variable rather than widening\nthe conflict-count tolerance, keeping the exact-match alarm for genuinely\nnew grammar conflicts intact.\n\n* ci: make Bison version parse robust (awk + explicit empty guard)\n\nAddress Copilot review on #2445: the previous grep-based parse could\nsilently yield an empty version and fail with a confusing\n\u0027Bison  !\u003d 3.8.x\u0027 message. Parse the version field with awk and error\nexplicitly when it can\u0027t be determined."
    },
    {
      "commit": "09fce2ced3e93bd76e237211001ad332e44c4e8b",
      "tree": "99f7d0cd8e953fed950f8fed257bd380e7f7189a",
      "parents": [
        "7bfeb24cff957e443cdc91b79a76c6401e6f8428"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Thu Jul 02 13:00:47 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 02 10:00:47 2026 -0700"
      },
      "message": "Fix segfault and out-of-bounds reads in file loaders on malformed rows (#2453)\n\n* Fix segfault and out-of-bounds reads in file loaders on malformed rows\n\nload_edges_from_file() and load_labels_from_file() build their COPY\nparser with only format\u003dcsv and header\u003dfalse, so COPY uses its default\ncomma delimiter. A file delimited by anything else (or a malformed row)\nthen parses with an unexpected column count, and the loaders indexed the\nparsed fields without validating that count:\n\n- process_edge_row() reads the four fixed fields fields[0..3]\n  unconditionally. A non-comma-delimited edge file parses as a single\n  column, so fields[1..3] are out of bounds -\u003e segfault (issue #2449).\n- create_agtype_from_list()/_i() pair header[i] with fields[i] for all\n  i \u003c nfields, so a row with more fields than the header reads header[i]\n  out of bounds.\n\nAdd bounds validation that turns these into clear errors:\n\n- Edge header must have \u003e\u003d 4 columns; a smaller count almost always\n  means the wrong delimiter, so the error carries a hint.\n- Each edge row must have \u003e\u003d 4 columns and no more than the header\u0027s.\n- Each label row must have no more than the header\u0027s column count.\n\nRows with fewer trailing columns than the header remain allowed, matching\nexisting behavior (exercised by the existing conversion tests).\n\nThis closes the segfault and out-of-bounds reads. The silent mis-parsing\nof a non-comma file whose header and rows share the same (wrong) column\ncount is not detectable here; adding a delimiter option to the load\nfunctions is a separate follow-up.\n\nAdds a regression test in age_load using a pipe-delimited edge file.\n\nAddresses #2449.\n\n* loader guards: clarify error wording and add per-row regression coverage\n\nAddress review feedback on the nfields guards:\n\n- Error messages now say \"the header\u0027s %d columns\" (was \"the header\u0027s %d\"),\n  making the count\u0027s unit explicit.\n- Add regression cases exercising the per-row guards, which previously only\n  had coverage for the mis-delimited-header path:\n    * an edge row with fewer than 4 columns\n    * an edge row with more columns than the header\n    * a label row with more columns than the header\n  Each asserts a clean ERROR (these were the out-of-bounds reads the guards\n  now catch)."
    },
    {
      "commit": "7bfeb24cff957e443cdc91b79a76c6401e6f8428",
      "tree": "63e14b3e65bc860e626c0fbf23f008366db05943",
      "parents": [
        "fafdaa1c0ef5f4ab8ff09482f20a382b845dc9e5"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Thu Jul 02 09:58:56 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 02 12:58:56 2026 -0400"
      },
      "message": "Support outer references in reduce() fold bodies (#2448)\n\nAllow a reduce(acc \u003d init, var IN list | body) fold body to reference\nloop-invariant values from the enclosing query -- outer-query variables and\ncypher() parameters -- in addition to the accumulator and element. These were\npreviously rejected with ERRCODE_FEATURE_NOT_SUPPORTED.\n\nHow it works\n------------\nThe fold body is still compiled to a standalone expression evaluated by\nage_reduce_transfn, so an outer reference (which cannot be evaluated there)\nis captured at transform time and supplied as a value:\n\n  - After the accumulator and element are rewritten to PARAM_EXEC params 0 and\n    1, transform_cypher_reduce() walks the body and replaces each loop-invariant\n    outer reference -- one that references an outer Var or a cypher() $parameter\n    but not the accumulator/element -- with a new PARAM_EXEC param 2, 3, ... in\n    body order. Capture is at leaf granularity: only the bare outer value is\n    hoisted out of the body, while the operators, function calls and\n    CASE/AND/OR/coalesce branches around it stay in the serialized body.\n    Because a captured value becomes an aggregate argument that the executor\n    evaluates eagerly and unconditionally, hoisting a whole computed subtree\n    (for example \"1/z\" under a never-taken CASE branch) would defeat the fold\u0027s\n    short-circuiting; capturing only the leaf keeps evaluation under the body\u0027s\n    own control flow. The one exception is an outer reference that is not itself\n    agtype-typed -- most commonly the graphid inside a graph vertex/edge\n    variable -- whose smallest enclosing agtype-typed subtree is captured whole,\n    since it cannot stand alone as an agtype[] extra.\n  - The captured expressions are passed to the aggregate as a trailing\n    agtype[] argument; age_reduce(agtype, text, agtype, agtype[]) and its\n    transition function gain this argument.\n  - age_reduce_transfn sizes its param array to 2 + the number of captures and\n    binds the captured values to params 2.. on every row. Because the captures\n    are evaluated in the outer query context as ordinary aggregate arguments, a\n    correlated capture is re-evaluated per group, so an outer value that varies\n    per row (for example under UNWIND) is folded with the correct value.\n    Each capture slot is rebound on every row, and the trailing extras\n    argument is read only when the aggregate actually passes it (PG_NARGS),\n    keeping the transition safe under direct age_reduce() SQL calls and an\n    older 4-argument signature.\n\nThis keeps the no-core-patch design: the body is still a serialized standalone\nexpression, and the only new machinery is the captured-value plumbing.\n\nStill rejected\n--------------\nSubqueries in the body (including a nested reduce()) and aggregate functions\nremain unsupported and raise a clean ERRCODE_FEATURE_NOT_SUPPORTED error: a\nsubquery cannot be planned as a plain aggregate argument, and an aggregate in a\nper-element fold is undefined per the openCypher specification.\n\nTests\n-----\nage_reduce gains an \"Outer references in the fold body\" section covering a\nplain outer variable, an outer variable used as a multiplier, two distinct\nouter variables, a property of an outer graph variable, the same outer variable\nreferenced more than once, a property of an outer map, a subexpression that\nmixes an outer reference with the element (only the loop-invariant part is\ncaptured), an outer reference inside a CASE branch of the body, a NULL outer\nvalue propagating through the fold, multiple captures mixing a NULL and a\nnon-NULL outer value, an outer variable that changes per row (captured per\ngroup), and a cypher() parameter supplied via a prepared statement.\n\nA \"Short-circuit evaluation is preserved for outer references in the body\"\nsection verifies that a guarded outer sub-expression is not evaluated on a\nbranch that is not taken: a never-taken CASE THEN branch, a never-taken CASE\nELSE branch, an OR and an AND that short-circuit, and a coalesce -- each of\nwhich would divide by zero if the outer \"1/w\" were hoisted into an eagerly\nevaluated aggregate argument -- plus a guarded branch that is taken and\nevaluates its outer division normally.\n\nThe previously-rejected outer-variable case is moved out of the not-supported\nsection, which now covers a nested reduce() (any subquery in the body is\nunsupported) and an aggregate in the body.\n\nThe same change also broadens the base reduce() coverage with value-type folds\n(a float accumulator, negative numbers, a map accumulator passed through\nunchanged, and list elements indexed in the body), function calls in the fold\nbody (a scalar function over the element and the list itself produced by a\nfunction), reduce() composed with surrounding expressions (consumed by another\nfunction and used in a comparison), and syntax-error checks for each required\npiece of the form -- the \"\u003d init\", \", var IN list\", and \"| body\" clauses, plus\na rejected qualified iterator variable. 42/42 installcheck pass.\n\nCo-authored-by: Copilot \u003ccopilot@github.com\u003e\n\nmodified:   age--1.7.0--y.y.y.sql\nmodified:   regress/expected/age_reduce.out\nmodified:   regress/sql/age_reduce.sql\nmodified:   sql/age_aggregate.sql\nmodified:   src/backend/parser/cypher_clause.c\nmodified:   src/backend/utils/adt/agtype.c"
    },
    {
      "commit": "fafdaa1c0ef5f4ab8ff09482f20a382b845dc9e5",
      "tree": "76d9cf2d945833b7941100653e7737fd74f79391",
      "parents": [
        "513767136a6e6660f6549c9c4b46f9524de1aef9"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Thu Jul 02 10:39:37 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jul 02 07:39:37 2026 -0700"
      },
      "message": "Fix stack overflow and precision loss in toFloatList() conversion (#2451)\n\ntoFloatList()\u0027s AGTV_FLOAT branch formatted each element with\nsprintf(buffer, \"%f\", ...) into a fixed 64-byte stack buffer and then\nre-parsed the string back into a float. This had two defects:\n\n  1. Stack overflow. \"%f\" prints the full integer part with no width\n     limit, so a large magnitude overflows the 64-byte buffer. The value\n     is query-reachable: RETURN toFloatList([1.0e308]) needs ~317 bytes\n     (309 integer digits + \".000000\") and smashes the stack. This is the\n     issue reported in #2410.\n\n  2. Precision loss. \"%f\" emits only 6 fractional digits, so the\n     format-and-reparse round trip was lossy -- toFloatList([0.123456789])\n     returned 0.123457.\n\nThe element is already a float8, so the whole format/reparse step is\nunnecessary. Assign elem-\u003eval.float_value directly. This removes the\nstack buffer entirely (no magic buffer size to justify) and fixes both\nthe overflow and the precision loss at once.\n\nAlso harden toStringList(): its \"%.*g\"/\"%ld\" conversions use bounded\nformats and were never overflow-prone, but switch them from sprintf to\nsnprintf as defensive depth.\n\nAdd regression coverage to regress/sql/expr.sql for both the large\nmagnitude case (no overflow) and precision preservation.\n\nThis reimplements the fix originally proposed by David Christensen in\n#2410, whose report identified the sprintf overflow.\n\nCo-authored-by: David Christensen \u003cdavid.christensen@snowflake.com\u003e"
    },
    {
      "commit": "513767136a6e6660f6549c9c4b46f9524de1aef9",
      "tree": "6e2727de715ab868cc8ef8de42241d45da277d95",
      "parents": [
        "54668e1b50aa22e76b8b522ef4531134c80c7ca2"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Wed Jul 01 19:01:42 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jul 01 22:01:42 2026 -0400"
      },
      "message": "Fix Node.js driver CI build broken by @types/node drift (#2452)\n\nThe Node.js driver CI (npm install -\u003e npm run build -\u003e tsc) failed with\nparser errors in node_modules/@types/node/ffi.d.ts (TS1139/TS1005/TS1109/\nTS1128). package-lock.json is gitignored, so CI resolves dependencies\npurely from package.json. @types/node was only pulled transitively via a\nwildcard range (@types/pg and jest depend on @types/node@*), so a fresh\ninstall grabbed the latest (26.x). That version uses `const` type\nparameters (a TypeScript 5.0 feature) in ffi.d.ts, which typescript@4.9\ncannot parse. skipLibCheck does not suppress these parser-level errors.\n\nThe runtime Node version is unrelated: @types/node is resolved from the\nnpm dependency graph, not the Node.js runtime.\n\nFix:\n- Add a bounded direct devDependency \"@types/node\": \"^20.19.0\" so a fresh\n  install constrains the typings to the Node 20 LTS line, which is\n  compatible with typescript@4.9 and keeps the toolchain consistent\n  (eslint 7 / typescript-eslint 4 / TS 4.9 / Node 20 typings).\n- Pin CI to Node 20 (setup-node@v4, node-version: 20) for reproducibility\n  and to match the pinned typings; replaces the deprecated setup-node@v3\n  and floating node-version: latest.\n\nVerified: a clean, no-lockfile install (matching CI) now resolves\n@types/node@20.19.43 and tsc builds successfully.\n\nCo-authored-by: Copilot \u003ccopilot@github.com\u003e\n\nmodified:   .github/workflows/nodejs-driver.yaml\nmodified:   drivers/nodejs/package.json"
    },
    {
      "commit": "54668e1b50aa22e76b8b522ef4531134c80c7ca2",
      "tree": "1cc735b70c9f7c1b17c0ae18e0758a0bcec7e247",
      "parents": [
        "fe48740d8597710e54b195176de332f7e95910e1"
      ],
      "author": {
        "name": "Prashant Chinnam",
        "email": "5108573+crprashant@users.noreply.github.com",
        "time": "Mon Jun 29 10:16:57 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 29 10:16:57 2026 -0700"
      },
      "message": "Preserve null-valued keys in map literals (#2391) (#2412)\n\n* Preserve null-valued keys in map literals (#2391)\n\nMap literals such as RETURN {a: null} previously dropped keys whose\nvalues were null, producing {} instead of {\"a\": null}. This\ndiverged from the openCypher / Neo4j semantics where map literals\npreserve every key the user wrote, including those bound to null.\n\nRoot cause: cypher_map.keep_null defaulted to false (zero-initialised),\nso the grammar-produced node fed agtype_build_map_nonull, which strips\nnull entries. Call sites that legitimately need strip-null semantics\n(CREATE node/edge property maps and SET \u003d assignments) already set\nkeep_null\u003dfalse explicitly, and the MATCH pattern path sets it to true\nexplicitly. Flipping the grammar default to true therefore only affects\nthe cases that were buggy (bare map expressions and nested map values),\nand leaves CREATE/SET behaviour unchanged.\n\nTwo preexisting tests encoded the old buggy output and are updated:\nexpr.out (bare RETURN maps now keep the null value) and agtype.out\n(a nested map inside an orderability test no longer drops its null\nentry, shifting one row in the ORDER BY result). Dedicated regression\ncoverage for #2391 is added to regress/sql/expr.sql.\n\n* Address review: move \u0027End of tests\u0027 marker and drop order claim\n\n- Move \u0027End of tests\u0027 to after the Issue 2391 test block so it marks\n  the actual end of the file.\n- Remove \u0027in order\u0027 from the mixed-values comment since map key\n  ordering is not guaranteed.\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n* Update accessor EXPLAIN expected output for null-preserving map literals\n\nMap literals now build with agtype_build_map (keep_null) instead of\nagtype_build_map_nonull, so the accessor-optimization EXPLAIN plan in\nexpr.out must reflect the new function name. Also strip a stray trailing\nCRLF on the last line of expr.sql/expr.out that leaked a carriage return\ninto the regression output.\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n* Add nested-map write coverage and clarify top-level strip wording\n\nA reviewer noted the keep_null\u003dtrue default reaches further than the\nPR summary stated: CREATE / SET \u003d only override keep_null\u003dfalse on the\ntop-level property map, not recursively. A nested map value is its own\ncypher_map node, so it now inherits the new default and preserves its\nnull-valued keys (e.g. CREATE (n {a: {b: null}}) stores a -\u003e {\"b\": null}).\n\n- regress/sql/expr.sql, regress/expected/expr.out: pin the nested case\n  under a write with CREATE (n:Nested {a: {b: null}}), MATCH ... SET\n  n \u003d {a: {b: null}}, and a MATCH verify.\n- src/backend/parser/cypher_gram.y: clarify the map: rule comment to\n  state the CREATE / SET override is top-level only and nested map\n  values keep the null-preserving default.\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n\n---------\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e"
    },
    {
      "commit": "fe48740d8597710e54b195176de332f7e95910e1",
      "tree": "32755f6ca253ed8d1e4630e48939f16ea218c7eb",
      "parents": [
        "08da2cedca49cd3735743107e38876197b762425"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Mon Jun 29 12:57:18 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 29 09:57:18 2026 -0700"
      },
      "message": "Fix single-node labeled pattern expressions not filtering by label (#2443) (#2444)\n\nA single-node labeled pattern used as a boolean expression -- e.g.\n`WHERE (a:Person)`, `WHERE EXISTS((a:Person))` -- was accepted but did not\ntest the bound vertex\u0027s label. It desugars to an EXISTS sub-pattern, and\nmake_path_join_quals() returned early for vertex-only patterns\n(list_length(entities) \u003c 3), emitting no quals. With no edge to carry a\ncorrelation, the sub-pattern referenced nothing from the enclosing query,\nso the planner produced an uncorrelated one-time InitPlan that was trivially\ntrue whenever any vertex of that label existed -- the predicate matched every\nouter row.\n\nEmit an explicit label-id filter for a vertex-only pattern whose vertex\ncarries a non-default label and whose variable is declared in an ancestor\nparse state (i.e. a correlated reference). make_qual() builds a name-based id\nreference that resolves to the outer variable, so the filter both correlates\nthe sub-pattern to that variable and enforces the label. Freshly scanned,\nnon-correlated vertices (no ancestor binding) are untouched, so plain\nMATCH (a:Person) and \"does any X exist\" EXISTS checks are unaffected.\n\nAdd regression coverage in pattern_expression: WHERE (a:Person),\nWHERE NOT (a:Person), and EXISTS((a:Company)) against a graph with a\nnon-Person vertex. All 41 regression tests pass."
    },
    {
      "commit": "08da2cedca49cd3735743107e38876197b762425",
      "tree": "b9b31b5cd5f34713296b8ba63342988158029d61",
      "parents": [
        "ce073ef0c796c7ede90e5c9db24de297564eacce"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Mon Jun 29 06:48:05 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 29 09:48:05 2026 -0400"
      },
      "message": "Support relationship-type filters and a minimum hop count (#2442)\n\nSupport relationship-type filters and a minimum hop count in shortest_path SRFs\n\nage_shortest_path / age_all_shortest_paths gain two related capabilities,\nboth following openCypher / Neo4j semantics.\n\nRelationship-type filtering: the edge_types argument now accepts an array\nof types; an edge matches when its label is any one of the requested\ntypes. A bare string or a one-element array keeps the single-type\nbehaviour, an empty string/array or NULL means no filter, and an unknown\ntype matches nothing. sp_run_bfs takes an Oid set rather than a single\noid, and sp_compute_paths resolves the argument into that set.\n\nMinimum hop count: the new min_hops argument is a lower bound on the path\nlength. When it does not exceed the true shortest distance it imposes no\nconstraint, so the normal BFS shortest-path result is returned. When it\nexceeds the shortest distance, BFS cannot produce a qualifying path, so\nthe search falls back to the variable-length-edge depth-first engine\n(sp_minhops_fallback), which enumerates edge-distinct paths\n(relationship-uniqueness / trail semantics) and returns the shortest\npath(s) whose length is at least min_hops. This regime permits revisiting\na vertex and closed walks back to the start, but never reusing an edge. A\nprivate memory context bounds the search and a cost guard caps the number\nof examined paths, raising PROGRAM_LIMIT_EXCEEDED (with a hint to bound the\nsearch with a maximum hop count) when the cap is exceeded. The hard regime\ncombined with multiple relationship types is unsupported, because the VLE\nengine matches a single label; that case raises FEATURE_NOT_SUPPORTED.\n\nRegression coverage spans single- and multi-type filters, directed and\nundirected reachability, multiplicity of equal-length paths, max_hops\nbounds, NULL and non-existent endpoints, and both min_hops regimes,\nincluding a vertex-revisiting longer path (sp_revisit) and a closed-walk\ncycle back to the start (sp_tri). The in-cypher() Tier 1 call forms are\nexercised as well.\n\nReview feedback addressed:\n\n1. Error messages now report the function actually called. age_shortest_path\n   and age_all_shortest_paths share their argument-resolution helpers, which\n   hard-coded an \"age_shortest_path\" prefix regardless of the caller; the\n   caller\u0027s name is now threaded through so each function reports its own\n   (this also corrects a mislabeled multi-type min_hops error). A new\n   regression case (sp_errname) pins the behaviour for both functions.\n\n2. age_all_shortest_paths now bounds the number of materialized result paths.\n   The shortest-path DAG can contain exponentially many equal-length paths,\n   all built up front before the first row streams; enumeration is capped at\n   SP_MAX_RESULT_PATHS (1,000,000), raising PROGRAM_LIMIT_EXCEEDED with a hint\n   to narrow the search, mirroring the existing min-hops candidate cap.\n\n3. The BFS search state (visited table, frontier queue, predecessor multiset,\n   and intermediate path arrays) now lives in a private scratch memory context\n   that is deleted once the surviving result Datums are built in the SRF\n   context, rather than persisting in multi_call_memory_ctx for the life of\n   the SRF. This bounds peak memory to the result set plus one search and\n   matches the pattern sp_minhops_fallback already used.\n\n4. A second review round tightened memory hygiene and reporting: the\n   pnstrdup\u0027d relationship-type name is freed once resolved to an oid (it was\n   retained for the life of the SRF) in both the array and scalar cases; the\n   invalid-direction error now carries the called function\u0027s name like the\n   other argument errors; the min-hops fallback\u0027s private context is renamed\n   to a caller-neutral \"age shortest path minhops\" (it is shared by both\n   SRFs); and the multi-type label-filter comment is corrected to note that an\n   unknown type merely contributes no matches -- known types in the same set\n   still match, and only an all-unknown set leaves just the zero-length path.\n\n41/41 installcheck.\n\nCo-authored-by: Copilot \u003ccopilot@github.com\u003e\n\nmodified:   regress/expected/age_shortest_path.out\nmodified:   regress/sql/age_shortest_path.sql\nmodified:   src/backend/utils/adt/age_vle.c"
    },
    {
      "commit": "ce073ef0c796c7ede90e5c9db24de297564eacce",
      "tree": "226c93a54972b7cb5842ab257805182c129b1036",
      "parents": [
        "92e48e9b907f491c92db487790c1624dba23b812"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Mon Jun 29 06:47:55 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 29 09:47:55 2026 -0400"
      },
      "message": "resolve subgraph staging sequences via regclass (#2446)\n\nThe vertex/edge staging copies in create_subgraph() generated new\ngraphids with nextval(%L), which binds the sequence as a string literal\nand invokes the nextval(text) overload. That re-resolves the\nschema-qualified sequence name on each call.\n\nCast the literal to regclass (nextval(%L::regclass)) so the sequence is\nresolved once to its OID, matching how AGE defines its label id defaults\n(nextval(\u0027schema.seq\u0027::regclass)). Applied to both the vertex and edge\nstaging queries, in sql/age_subgraph.sql and the identical body in the\nage--1.7.0--y.y.y.sql upgrade template so the upgrade-path catalog\ncomparison still matches.\n\nBehavior is unchanged; all 38 regression tests pass against PostgreSQL 18.\n\nAddresses Copilot review feedback on apache/age#2441.\n\nCo-authored-by: GitHub Copilot (Claude Opus 4.8) \u003c[email protected]\u003e\n\nmodified:   age--1.7.0--y.y.y.sql\nmodified:   sql/age_subgraph.sql"
    },
    {
      "commit": "92e48e9b907f491c92db487790c1624dba23b812",
      "tree": "556a2d816aa0e42eac631ba2a1eae10695543ac8",
      "parents": [
        "2bc8e95f00193e33a0ba8cf8839d7a97f65beeb8"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Jun 26 15:54:54 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 26 15:54:54 2026 -0700"
      },
      "message": "Add reduce() list folding function (#2435)\n\nImplement the openCypher reduce(acc \u003d init, var IN list | body) expression,\nwhich folds an arbitrary expression over a list, threading an accumulator\nacross the elements in list order. This closes a long-standing gap (reduce()\nwas previously unsupported) and works both at the SQL top level and inside a\ncypher() RETURN/WHERE.\n\nImplementation\n--------------\nreduce() is desugared, at transform time, into a correlated scalar subquery\nover a new ordered aggregate rather than a new executor node, so no PostgreSQL\ncore changes are required:\n\n    CASE WHEN list IS NULL THEN NULL\n         ELSE COALESCE((SELECT ag_catalog.age_reduce(\n                            \u003cinit\u003e, \u0027\u003cserialized-body\u003e\u0027::text,\n                            r.elem ORDER BY r.ord)\n                        FROM unnest(\u003clist\u003e) WITH ORDINALITY AS r(elem, ord)),\n                       \u003cinit\u003e)\n    END\n\n  - A new cypher_reduce extensible node carries the accumulator/element names\n    and the init/list/body expressions (grammar production, keyword, and the\n    copy/out/read serialization plumbing).\n  - The fold body is transformed against a throwaway two-column agtype\n    namespace, its accumulator and element Var references are rewritten to\n    PARAM_EXEC params 0 and 1, and it is serialized with nodeToString() into a\n    text argument.\n  - age_reduce_transfn (a custom agtype aggregate transition function)\n    deserializes and compiles the body once per group with ExecInitExpr, then\n    evaluates it per element with ExecEvalExpr, rebinding the two params. The\n    body is normalized to agtype at transform time so a boolean or other\n    non-agtype result cannot be misread as a by-reference Datum.\n\nSemantics\n---------\n  - List order is preserved (unnest WITH ORDINALITY + aggregate ORDER BY).\n  - An empty list yields the initial value; a NULL list yields NULL.\n  - The list and initial value may reference outer-query variables (e.g.\n    reduce(total \u003d 0, n IN nodes(p) | total + n.age)); the body may reference\n    only the accumulator and element.\n  - Arithmetic, string, list-building, boolean/comparison (AND/OR/\u003d/\u003e), CASE,\n    and element property-access bodies are all supported.\n  - Outer-variable, query-parameter, nested-reduce, and aggregate references\n    inside the body raise a clean ERRCODE_FEATURE_NOT_SUPPORTED error.\n  - reduce is registered as a safe keyword so it remains usable as a property\n    or map key, preserving backward compatibility.\n\nTests\n-----\nAdds the age_reduce regression test (registered in the install SQL and the\nupgrade template so age_upgrade passes), covering: arithmetic/product/string\nfolds; order sensitivity; empty/NULL list; NULL element and NULL init;\nlist-building and CASE bodies; boolean and comparison bodies; element property\naccess; multiple and nested (in list/init) reduce(); reduce() in boolean\nexpressions, WHERE, and list comprehensions; folds over collected nodes and\nnode list properties; the not-supported rejections; and reduce as a map key.\n\nFollowing reviewer feedback, three further semantics-coverage gaps are\npinned directly so the mechanisms that make the aggregate desugaring\ncorrect are exercised by tests rather than only correct by inspection:\n  - A fold body that produces null mid-fold and then recovers: the\n    agtype \u0027null\u0027 running state is a readable value, so a later element\n    folds back out of it (distinct from \"null propagates to a null\n    result\", which was already covered).\n  - An empty list with a NULL initial value: COALESCE(\u003cno rows\u003e, init)\n    yields NULL, kept distinct from a body that legitimately folds to\n    agtype \u0027null\u0027, which must not be resurrected to the initial value.\n  - A type error and a runtime division-by-zero error in the body: both\n    abort cleanly out of the standalone per-element evaluator rather\n    than corrupting the running aggregate state.\n\nAll multi-row results are ordered. 42/42 installcheck pass.\n\nFuture work\n-----------\nThe body restriction (accumulator and element only) is a property of the\nstandalone expression evaluation and can be relaxed without core changes:\n  - Allow loop-invariant outer-variable and cypher $parameter references in\n    the body by capturing them as additional eager aggregate arguments bound\n    to extra param slots.\n  - Support a nested reduce() inside the body via an SPI-based evaluation\n    fallback for subquery-bearing bodies.\nAggregates inside the body remain intentionally unsupported, matching the\nopenCypher specification.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\n\nmodified:   Makefile\nmodified:   age--1.7.0--y.y.y.sql\nnew file:   regress/expected/age_reduce.out\nnew file:   regress/sql/age_reduce.sql\nmodified:   sql/age_aggregate.sql\nmodified:   src/backend/nodes/ag_nodes.c\nmodified:   src/backend/nodes/cypher_copyfuncs.c\nmodified:   src/backend/nodes/cypher_outfuncs.c\nmodified:   src/backend/nodes/cypher_readfuncs.c\nmodified:   src/backend/parser/cypher_analyze.c\nmodified:   src/backend/parser/cypher_clause.c\nmodified:   src/backend/parser/cypher_gram.y\nmodified:   src/backend/utils/adt/agtype.c\nmodified:   src/include/nodes/ag_nodes.h\nmodified:   src/include/nodes/cypher_copyfuncs.h\nmodified:   src/include/nodes/cypher_nodes.h\nmodified:   src/include/nodes/cypher_outfuncs.h\nmodified:   src/include/nodes/cypher_readfuncs.h\nmodified:   src/include/parser/cypher_kwlist.h"
    },
    {
      "commit": "2bc8e95f00193e33a0ba8cf8839d7a97f65beeb8",
      "tree": "173625434d94010a60b97f9b3971b5ba6d792277",
      "parents": [
        "5abb02f8033db15d43a7dabce367155978294806"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Mon Jun 22 12:34:31 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 22 09:34:31 2026 -0700"
      },
      "message": "Support pattern expressions as boolean expressions (#2360)\n\n* Support pattern expressions in WHERE clause via GLR parser (issue #1577)\n\nEnable bare graph patterns as boolean expressions in WHERE clauses:\n\n  MATCH (a:Person), (b:Person)\n  WHERE (a)-[:KNOWS]-\u003e(b)        -- now valid, equivalent to EXISTS(...)\n  RETURN a.name, b.name\n\nPreviously, this required wrapping in EXISTS():\n  WHERE EXISTS((a)-[:KNOWS]-\u003e(b))\n\nThe bare pattern syntax is standard openCypher and is used extensively\nin Neo4j.  Its absence was the most frequently cited migration blocker.\n\nImplementation approach:\n- Switch the Cypher parser from LALR(1) to Bison GLR mode.  GLR handles\n  the inherent ambiguity between parenthesized expressions \u0027(\u0027 expr \u0027)\u0027\n  and graph path nodes \u0027(\u0027 var_name label_opt props \u0027)\u0027 by forking the\n  parse stack and discarding the failing path.\n- Add anonymous_path as an expr_atom alternative with %dprec 1 (lower\n  priority than expression path at %dprec 2).  The action wraps the\n  pattern in a cypher_sub_pattern + EXISTS SubLink, reusing the same\n  transform_cypher_sub_pattern() machinery as explicit EXISTS().\n- Extract make_exists_pattern_sublink() helper shared by both\n  EXISTS(pattern) and bare pattern rules.\n- Fix YYLLOC_DEFAULT to use YYRHSLOC() for GLR compatibility.\n- %dprec annotations on expr_var/var_name_opt resolve the reduce/reduce\n  conflict between expression variables and pattern node variables.\n\nConflict budget: 7 shift/reduce (path extension vs arithmetic on -/\u003c),\n3 reduce/reduce (expr_var vs var_name_opt on )/}/\u003d).  All are expected\nand handled correctly by GLR forking + %dprec disambiguation.\n\nAll 32 regression tests pass (31 existing + 1 new).  New\npattern_expression test covers: bare patterns, NOT patterns, labeled\nnodes, AND/OR combinations, left-directed patterns, anonymous nodes,\nmulti-hop patterns, EXISTS() backward compatibility, and non-pattern\nexpression regression checks.\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Address Copilot review: comment placement, %expect docs, test wording\n\n1. Move \"Helper function to create an ExplainStmt node\" comment from\n   above make_exists_pattern_sublink() to above make_explain_stmt()\n   where it belongs.\n\n2. Add block comment documenting the %expect/%expect-rr conflict\n   budget: 7 S/R from path vs arithmetic on - and \u003c, 3 R/R from\n   expr_var vs var_name_opt on ) } \u003d.\n\n3. Clarify test comment: \"Regular expressions\" -\u003e \"Regular (non-pattern)\n   expressions\" to avoid confusion with regex.\n\nRegression test: pattern_expression OK.\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Address Copilot round 3: broaden scope, remove %expect fragility\n\n- Pattern expressions are now accepted anywhere an expr is valid\n  (RETURN, WITH, SET, CASE, boolean combinations), not only WHERE.\n  This matches openCypher semantics and documents the broader surface\n  area that was already implicitly enabled by adding anonymous_path\n  to expr_atom.  Added regression tests for each new context:\n  RETURN projection (bare and AS-aliased), mixed with other\n  projections, CASE WHEN, boolean AND/OR combinators, SET to\n  persist a computed boolean property, and WITH ... WHERE pipeline.\n\n- Remove the hardcoded `%expect 7` / `%expect-rr 3` conflict budget\n  from cypher_gram.y.  The exact conflict counts can drift across\n  Bison versions and distros, which would break builds even though\n  the grammar is correct (GLR handles the conflicts at runtime via\n  fork + %dprec).  Instead, pass -Wno-conflicts-sr / -Wno-conflicts-rr\n  via BISONFLAGS in the Makefile so the build stays clean without\n  binding us to a specific Bison release.  Kept a block comment in\n  the grammar explaining why GLR conflicts are expected and how\n  they resolve.\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Address jrgemignani review: keep -Werror, restore %expect budget\n\nReverts the broad `-Werror` drop and the no-%expect approach from the\nprior round on jrgemignani\u0027s request.  The earlier framing — that\nconflict counts drift across Bison versions, so %expect is fragile —\novercorrected: it removed the only build-time alarm bell for unintended\nnew conflicts.\n\nMakefile: keep -Werror so any unexpected Bison warning (unused rules,\nundeclared types, etc.) still fails the build; downgrade only the two\nconflict categories to plain warnings via -Wno-error\u003dconflicts-sr\n-Wno-error\u003dconflicts-rr.  pgxs auto-adds -Wno-deprecated, so existing\n%name-prefix\u003d / %pure-parser directives remain non-erroring.\n\ncypher_gram.y: add `%expect 7` and `%expect-rr 3` matching the\nBison 3.8.2 totals.  Bison treats %expect as exact-match, not as a\nceiling — any deviation fails the build and forces an audit of the new\nconflicts.  Comment updated to reflect that future Bison versions\nreporting different counts should bump the numbers explicitly with a\nversion note in the commit message, rather than removing the directive.\n\nNo grammar or runtime change.  Cassert installcheck 34/34 AGE tests\ngreen.\n\n* Add follow-up regression coverage for pattern expressions (#2360)\n\nAddresses the non-blocking test-coverage follow-ups from the review:\npattern expressions in additional contexts opened up by allowing\nanonymous_path as an expr_atom.\n\nNew cases (all verified against a PG18 build):\n- Single-node pattern on a bound variable (a:Label). Documented as an\n  EXISTS existence check, NOT an openCypher label predicate: a matching\n  label is always true, and a non-matching label hits AGE\u0027s pre-existing\n  \"multiple labels for variable\" restriction (captured as expected error).\n- Pattern expressions inside list and map literals.\n- Pattern expressions as function arguments: collect() shows correct\n  per-row booleans; count() counts all rows (non-null bool) -- documented\n  so the value is not mistaken for a bug.\n- Pattern expression in OPTIONAL MATCH ... WHERE (null-preserving).\n- EXISTS() and a bare pattern combined in one predicate.\n\nmake installcheck: 33/33 green.\n\n---------\n\nCo-authored-by: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "5abb02f8033db15d43a7dabce367155978294806",
      "tree": "a972d2cdb884f97486d91eedd22e2f09fd9d10db",
      "parents": [
        "3cc74a4f75252e5a9969f00bad68e3b333f01c5f"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sun Jun 21 14:35:36 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jun 21 23:35:36 2026 +0200"
      },
      "message": "Add shortest_path / all_shortest_paths SRFs (#2430)\n\nAdd two C set-returning functions that compute unweighted (hop-count)\nshortest paths over the cached global graph adjacency via BFS, callable\nboth at the SQL top level and inside a cypher() RETURN:\n\n  - age_shortest_path(...)       -\u003e the single shortest path (0 or 1 rows)\n  - age_all_shortest_paths(...)  -\u003e every shortest path, one per row\n\nThe signature follows the natural Cypher argument order\n(graph, start, end, edge_types, direction, min_hops, max_hops), registered\nin sql/agtype_typecast.sql (install) and age--1.7.0--y.y.y.sql (upgrade).\nUnimplemented parameters fail loudly: multiple relationship types and a\nnon-zero min_hops raise ERRCODE_FEATURE_NOT_SUPPORTED. A single edge type\n(string or one-element array) is honored, and a NULL endpoint yields no\nrows per Cypher null semantics (wrong-typed endpoints / NULL graph still\nerror).\n\nTo call the SRFs inside a cypher() RETURN, transform_cypher_return now sets\nquery-\u003ehasTargetSRFs (it was the only results-producing clause that didn\u0027t,\nso the planner never added a ProjectSet node), and transform_FuncCall\nauto-prepends the graph name for snake_case shortest_path /\nall_shortest_paths. camelCase names are reserved for the future native\ngrammar.\n\nRobustness:\n  - BFS guards against non-existent endpoints (returns 0 rows instead of\n    crashing) and honors CHECK_FOR_INTERRUPTS.\n  - An unknown edge label now matches no edges instead of silently\n    traversing all of them (get_label_relation returns InvalidOid).\n\nAdds the age_shortest_path regression test (directed/undirected, label\nfiltering, parallel edges, self-loops, max_hops, the not-supported stubs,\nNULL and non-existent endpoint/graph guards).\n\n38/38 installcheck pass.\n\nCo-authored-by: Copilot \u003ccopilot@github.com\u003e\n\nmodified:   Makefile\nmodified:   age--1.7.0--y.y.y.sql\nmodified:   sql/agtype_typecast.sql\nmodified:   src/backend/parser/cypher_clause.c\nmodified:   src/backend/parser/cypher_expr.c\nmodified:   src/backend/utils/adt/age_vle.c\nnew file:   regress/expected/age_shortest_path.out\nnew file:   regress/sql/age_shortest_path.sql"
    },
    {
      "commit": "3cc74a4f75252e5a9969f00bad68e3b333f01c5f",
      "tree": "79b5e95dccb8cae45ecf9ce9f0b8fafcef0aaa7a",
      "parents": [
        "01ea79de67b203b957658875f3ca0c4189a99207"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sun Jun 21 14:34:37 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jun 21 23:34:37 2026 +0200"
      },
      "message": "cypher_with: add ORDER BY to non-deterministic RETURN queries (#2436)\n\nSeveral cypher_with regression queries RETURN multiple rows without an\nORDER BY, so their row order depends on heap/scan order and can vary\nbetween runs, build types, and platforms. Add ORDER BY ASC to those\nqueries so the expected output is stable. Ordering keys use id() (a\nsingle int64 that bypasses the locale-sensitive string comparison path\nand is reproducible from the test\u0027s deterministic setup order), or the\nprojected path/scalar where that is what the query returns. Where the\nunderlying vertex/edge was dropped by a WITH projection, its id is\nthreaded through as an alias rather than reordering the projection.\n\nFull audit of cypher_with: all 23 multi-row result blocks were checked.\nAfter this change, every multi-row, non-EXPLAIN RETURN is deterministically\nordered. The two remaining unordered multi-row blocks are left as-is:\n- \"RETURN lbl\" returns two identical \"Person\" rows, so order cannot drift;\n- the 13 EXPLAIN (VERBOSE, COSTS OFF) plan blocks emit a fixed serial plan\n  (no parallel/gather nodes), so their row order is already deterministic.\n\nThis is a test-only change (regress/sql/cypher_with.sql and\nregress/expected/cypher_with.out); no extension C code or SQL is modified.\nRow counts are unchanged (pure reordering).\n\nAll 37 regression tests pass (installcheck) on PostgreSQL 18.3.\n\nCo-authored-by: GitHub Copilot \u003cnoreply@github.com\u003e\n\nmodified:   regress/expected/cypher_with.out\nmodified:   regress/sql/cypher_with.sql"
    },
    {
      "commit": "01ea79de67b203b957658875f3ca0c4189a99207",
      "tree": "5529666a57ef08b7e49d094f5a6cbf1848c4a09b",
      "parents": [
        "581d236eb300b94c75ac26bc2e319c6152987140"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sun Jun 21 14:33:12 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jun 21 23:33:12 2026 +0200"
      },
      "message": "Make ag_catalog ownership and built-in resolution explicit (#2440)\n\nAGE places all of its objects in the ag_catalog schema. Make the\nassumptions around that schema explicit so installs and upgrades behave\npredictably regardless of how a database is provisioned:\n\n- Ownership-checked install: CREATE EXTENSION age installs into\n  ag_catalog only when that schema does not already exist under a\n  different owner, keeping ownership of AGE\u0027s catalog well-defined.\n- Deterministic name resolution: the pg_upgrade helper functions resolve\n  built-ins from pg_catalog first and schema-qualify their\n  format()/hashtext() calls, so their behavior does not depend on what\n  else is defined in ag_catalog.\n- README note describing ag_catalog ownership and the install-time check.\n\nThe upgrade script applies the same helper changes so existing\ninstallations get them on ALTER EXTENSION UPDATE. Adds an\nextension_security regression test covering the ownership check and the\nqualified-call / search_path properties.\n\nAssisted-by: GitHub Copilot (Claude Opus 4.8)\n\nmodified:   Makefile\nmodified:   README.md\nmodified:   age--1.7.0--y.y.y.sql\nnew file:   regress/expected/extension_security.out\nnew file:   regress/sql/extension_security.sql\nmodified:   sql/age_main.sql\nmodified:   sql/age_pg_upgrade.sql\n\nResolved Conflicts: Makefile"
    },
    {
      "commit": "581d236eb300b94c75ac26bc2e319c6152987140",
      "tree": "1e57f31244053f00f30f4061ac55c70d789b0968",
      "parents": [
        "34483efaf1eb0724bf92adda18f06973a1f4a3e1"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sat Jun 20 09:58:14 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jun 20 18:58:14 2026 +0200"
      },
      "message": "Makefile: add installcheck-existing target and improve readability (#2437)\n\nAdd an \"installcheck-existing\" target that runs the regression suite\nagainst an already-running PostgreSQL server, complementing the default\n\"installcheck\" (which builds a private temp instance). It points\npg_regress at the server via the standard libpq variables\n(PGHOST/PGPORT/PGUSER; PGDATABASE defaults to contrib_regression) and\nlets pg_regress create the database and load the extension through\n--load-extension\u003dage. It deliberately avoids --use-existing -- that\noption skips database creation and disables --load-extension -- so no\nmanual CREATE EXTENSION step is required. The upgrade test (age_upgrade)\nis excluded because it stages synthetic extension files into the local\nsharedir that a running server would not see.\n\nReadability and maintainability improvements (no behavior change):\n- Derive age_sql from AGE_CURR_VER (read from age.control) so the\n  version number is defined in exactly one place.\n- Add section banners and a top-of-file layout/target index; group the\n  scattered upgrade-test pieces and move the ag_scanner flex rule in\n  with the other parser-generation rules.\n- Wrap the long REGRESS_OPTS and EXTRA_CLEAN assignments across lines.\n- Fix the DATA filter-out pattern to use a double dash\n  (age--%--y.y.y.sql) matching the actual template filename; the prior\n  single-dash pattern only matched via greedy \u0027%\u0027 expansion.\n- Anchor the age.control version regex (/^default_version/).\n- Replace the hardcoded \"31 tests\" comment with generic wording and add\n  a \"help\" target listing the common targets.\n\nVerified on PostgreSQL 18: make installcheck (temp instance) and\nmake installcheck-existing both pass; clean rebuild and make clean\nunaffected.\n\nCo-authored-by: GitHub Copilot \u003cnoreply@github.com\u003e\n\nmodified:   Makefile"
    },
    {
      "commit": "34483efaf1eb0724bf92adda18f06973a1f4a3e1",
      "tree": "7ce35ae65d99b95dfac85ee7b3e41131f3117e60",
      "parents": [
        "932d26f2543278ce9ce060b42d6765713aec27f2"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sat Jun 20 09:57:33 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jun 20 18:57:33 2026 +0200"
      },
      "message": "age_global_graph: stabilize regression tests (#2431)\n\nage_global_graph: stabilize regression tests under concurrent xid load\n\nWrap both vertex_stats() context-building phases in a single\nBEGIN ISOLATION LEVEL REPEATABLE READ; ... COMMIT; transaction so the\nthree calls share one snapshot. This prevents the snapshot-fallback path\nin is_ggctx_invalid() from purging an already-built graph context when\nconcurrent xid activity (autovacuum, parallel installcheck, replication,\nshared CI) advances the snapshot between calls, which would otherwise\nmake the targeted delete_global_graphs(name) checks return false instead\nof the expected true. Read Committed is insufficient because it acquires\na fresh snapshot per statement; REPEATABLE READ pins one snapshot for the\nwhole transaction.\n\nAlso add explicit ORDER BY id to the three direct-SQL label-table SELECTs\n(_ag_label_vertex x2, _ag_label_edge) that return multiple rows, so their\noutput no longer depends on heap scan order.\n\nThis is a test-only change (regress/sql/age_global_graph.sql and\nregress/expected/age_global_graph.out); no extension C code or SQL is\nmodified.\n\nAll 37 regression tests pass (installcheck) on PostgreSQL 18.3.\n\nCo-authored-by: GitHub Copilot \u003cnoreply@github.com\u003e\n\nmodified:   regress/expected/age_global_graph.out\nmodified:   regress/sql/age_global_graph.sql"
    },
    {
      "commit": "932d26f2543278ce9ce060b42d6765713aec27f2",
      "tree": "9cf7cd44190bdc8a3e11120a3ac1d6d9dcd2f3ab",
      "parents": [
        "310dc3f680a034d81baaf7176ca3359b67b44918"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sat Jun 20 09:56:48 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jun 20 18:56:48 2026 +0200"
      },
      "message": "cypher_vle: add ORDER BY to non-deterministic RETURN queries (#2434)\n\nSeveral VLE regression queries RETURN multiple rows without an ORDER BY,\nso their row order depends on traversal/scan order and can vary between\nruns and platforms. Add ORDER BY ASC to those queries (on the path,\nedge-list, or graphid as appropriate) so the expected output is stable.\nThe queries are pinned by path (p), edge list (e), or graphid\n(id(u)/id(v)/id(e[n])) depending on what each RETURN projects.\n\nFull audit of cypher_vle: all 38 multi-row result blocks were checked.\nAfter this change, every multi-row RETURN is deterministically ordered\nexcept the two SELECT * FROM show_list_use_vle(\u0027list01\u0027) calls, which are\nalready deterministic because the function body orders its results with\nRETURN v ORDER BY id(v) (added in #2417); their result blocks are\nunchanged by this commit.\n\nThis is a test-only change (regress/sql/cypher_vle.sql and\nregress/expected/cypher_vle.out); no extension C code or SQL is modified.\nRow counts are unchanged (pure reordering).\n\nAll 37 regression tests pass (installcheck) on PostgreSQL 18.3.\n\nCo-authored-by: GitHub Copilot \u003cnoreply@github.com\u003e\n\nmodified:   regress/expected/cypher_vle.out\nmodified:   regress/sql/cypher_vle.sql"
    },
    {
      "commit": "310dc3f680a034d81baaf7176ca3359b67b44918",
      "tree": "10084d0e4b8f9414300e2960aecf38d33fe4a75d",
      "parents": [
        "a17bf457fe5856e1e7f538400c67d8669d828e54"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sat Jun 20 09:55:09 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jun 20 18:55:09 2026 +0200"
      },
      "message": "feature: add create_subgraph() (#2441)\n\nAdd the feature create_subgraph() for materialized induced-subgraph\nextraction.\n\nAdd ag_catalog.create_subgraph(new_graph, from_graph, node_filter,\nrelationship_filter) which materializes a new, persistent, fully\nCypher-queryable AGE graph as the induced subgraph of an existing graph.\n\nSelection follows the graph-theory induced-subgraph definition as\noperationalized by Neo4j GDS gds.graph.filter():\n  * a vertex is kept iff node_filter holds (\u0027*\u0027 keeps all);\n  * an edge is kept iff relationship_filter holds AND both of its\n    endpoints were kept (no dangling edges).\n\nFilters are arbitrary Cypher predicates bound to `n` (nodes) and `r`\n(relationships) and are evaluated by AGE\u0027s own Cypher engine against the\nsource graph, so the full predicate language is available; label\nselection uses label(n)/label(r) since the match pattern is fixed.\n\nImplementation notes:\n  * Result is a real, ACID, registered graph (create_graph + create_v/\n    elabel), not a virtual view; it composes with cypher() and itself.\n  * Entity graphids are reassigned from the destination labels\u0027 own\n    sequences (graphid encodes a per-graph label id), and edge endpoints\n    are remapped through an old-\u003enew vertex map, enforcing the induced\n    rule via inner joins.\n  * Source label tables are read with FROM ONLY to avoid double-copying\n    children under PostgreSQL table inheritance.\n  * Properties of any agtype are preserved; self-loops and parallel\n    edges (multigraph structure) are retained.\n  * SECURITY INVOKER: reads respect the caller\u0027s table privileges and\n    RLS; the new graph is owned by the caller.\n  * Validates NULL/identical graph names, missing source, pre-existing\n    destination, and a reserved dollar-quote token in predicates.\n\nWire-up:\n  * sql/age_subgraph.sql (new) registered in sql/sql_files after\n    age_pg_upgrade; identical body added to age--1.7.0--y.y.y.sql so the\n    upgrade-path catalog comparison matches.\n  * regress/sql/subgraph.sql + expected output (new), added to REGRESS.\n    Covers full copy, vertex-induced, node+rel, label-only edge drop,\n    bipartite, empty result, composability, self-loops/parallel edges,\n    property fidelity, and error cases over a ~4500-vertex / 2000-edge\n    source graph.\n\nAll 38 regression tests pass against PostgreSQL 18.\n\nCo-authored-by: GitHub Copilot (Claude Opus 4.8) \u003c[email protected]\u003e\n\nmodified:   Makefile\nmodified:   age--1.7.0--y.y.y.sql\nnew file:   regress/expected/subgraph.out\nnew file:   regress/sql/subgraph.sql\nnew file:   sql/age_subgraph.sql\nmodified:   sql/sql_files"
    },
    {
      "commit": "a17bf457fe5856e1e7f538400c67d8669d828e54",
      "tree": "5b4c42a5cd1ba7d37c2b32988e5c1b7bc14f1c2a",
      "parents": [
        "14732bfaf4a63a1ac9775d7dc7a2c0eed3f47ab2"
      ],
      "author": {
        "name": "serdarmumcu",
        "email": "serdar.mumcu@udemy.com",
        "time": "Sat Jun 20 19:53:25 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jun 20 18:53:25 2026 +0200"
      },
      "message": "Make age extension usable from shared_preload_libraries (#2438)\n\nWhen AGE is loaded via shared_preload_libraries, its hooks\n(post_parse_analyze, set_rel_pathlist, object_access) are active\nbefore CREATE EXTENSION age is run. This causes errors when\nnon-Cypher queries trigger those hooks and ag_catalog does not\nyet exist.\n\nChanges:\n\n- Add is_age_extension_exist() with a relcache callback cache so that\n  checking pg_extension is not repeated on every hook invocation.\n- Guard post_parse_analyze, set_rel_pathlist, and object_access hooks\n  with is_age_extension_exist() so they become no-ops when the\n  extension is not installed.\n- Refactor ag_ProcessUtility_hook to detect CREATE/DROP EXTENSION age\n  and broadcast a relcache invalidation via\n  CacheInvalidateRelcacheByRelid(ExtensionRelationId) so other\n  backends update their cached extension state.\n- Wrap DROP EXTENSION processing in PG_TRY/PG_CATCH to restore\n  object_access_hook if the drop fails (e.g. dependent objects).\n- Skip _PG_init during pg_upgrade (IsBinaryUpgrade) to avoid hook\n  registration when the binary-upgrade machinery is running.\n- Add regression tests that verify hooks do not error when ag_catalog\n  schema is absent."
    },
    {
      "commit": "14732bfaf4a63a1ac9775d7dc7a2c0eed3f47ab2",
      "tree": "de5171bfe34086d070cbae6ea6a9f709c8019a23",
      "parents": [
        "12e2a31c6716073fa046efe6fd3b1f930efabca6"
      ],
      "author": {
        "name": "serdarmumcu",
        "email": "serdar.mumcu@udemy.com",
        "time": "Fri Jun 12 00:05:26 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jun 11 14:05:26 2026 -0700"
      },
      "message": "Fix locale-dependent string comparison in direct_field_access test (#2439)\n\nReplace \u0027test\u0027 \u003e \u0027TEST\u0027 with deterministic \u0027abd\u0027 \u003e \u0027abc\u0027. The original\ncomparison returns different results depending on system collation\nsettings (true under C locale, false under en_US.UTF-8)."
    },
    {
      "commit": "12e2a31c6716073fa046efe6fd3b1f930efabca6",
      "tree": "c3a31c98c86398d780556d27d72fe33ba2b0c41e",
      "parents": [
        "23cbe57a1eedee9167221a0359b09e538ff6fc87"
      ],
      "author": {
        "name": "Prashant Chinnam",
        "email": "5108573+crprashant@users.noreply.github.com",
        "time": "Thu Jun 11 06:58:30 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jun 11 06:58:30 2026 -0700"
      },
      "message": "Fix VLE [*0..N] zero-hop self-binding when edge label is missing (#2382) (#2419)\n\nA variable-length relationship pattern with a zero lower bound, e.g.\n`(p)-[:LABEL*0..N]-(f)`, must produce the zero-hop self-binding row\n(`f` \u003d `p`) regardless of whether any edge of `LABEL` exists in the\ngraph. This matches Neo4j/openCypher semantics. Previously, when the\nedge label did not exist in the label cache, AGE short-circuited the\nentire MATCH to zero rows (or NULL-extended rows for OPTIONAL MATCH).\n\nThe fix has three parts:\n\n1. parser/cypher_clause.c: A new helper `is_zero_lower_bound_vle()`\n   inspects the FuncCall produced by `build_VLE_relation()` and reports\n   whether the relationship is a zero-bound VLE. It is intentionally\n   defensive about the FuncCall shape so that any future parser changes\n   fall back to the existing short-circuit safely.\n\n   `match_check_valid_label()` and `path_check_valid_label()` now treat\n   a missing edge label as fatal only when the relationship requires at\n   least one edge of that label. Patterns mixing a zero-bound segment\n   with another impossible segment (e.g. `(a)-[:NOEXIST*0..1]-(b)-[:STILL_MISSING]-(c)`)\n   still correctly resolve to zero rows because the second segment\n   independently fails the label check.\n\n2. utils/adt/age_vle.c: `is_an_edge_match()` now returns false early\n   when the user requested a specific label that does not exist\n   (`edge_label_name !\u003d NULL \u0026\u0026 edge_label_name_oid \u003d\u003d InvalidOid`).\n   This prevents a zero-bound traversal of `[:NOEXIST*0..N]` from\n   incorrectly walking arbitrary other-label edges via the existing\n   \"no constraints -\u003e match all\" fast path. The zero-hop case itself\n   is unaffected because it is generated by `build_VLE_zero_container()`\n   without ever consulting `is_an_edge_match()`.\n\n3. regress/sql/cypher_vle.sql: Adds seven regression cases that lock\n   in the new behaviour, including the rubber-duck scenarios where\n   another label exists in the graph (must NOT be matched by the\n   missing-label VLE), where another segment is unsatisfiable\n   (must still produce zero rows), and where the label exists\n   (sanity check, unchanged behaviour)."
    },
    {
      "commit": "23cbe57a1eedee9167221a0359b09e538ff6fc87",
      "tree": "1629a0931e4967090ac89af2e44335a3aa48029b",
      "parents": [
        "639c8d5ac1cc930095caccb58b90dd65d9035e12"
      ],
      "author": {
        "name": "crdv7",
        "email": "49877075+crdv7@users.noreply.github.com",
        "time": "Wed Jun 10 01:44:15 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jun 09 10:44:15 2026 -0700"
      },
      "message": "fix: remove pthread_mutex that causes self-deadlock on VLE queries (#2433)\n\nThe pthread_mutex in manage_GRAPH_global_contexts() causes permanent\nself-deadlock when ereport(ERROR) triggers siglongjmp while the mutex\nis held, skipping pthread_mutex_unlock(). Any subsequent VLE query on\nthe same backend connection hangs forever in pthread_mutex_lock() with\n__owner \u003d\u003d own PID.\n\nThe mutex was introduced in PR #1881 (fix for issue #1878) but is\nunnecessary: it protects a process-local static variable in\nPostgreSQL\u0027s single-threaded backend model where no concurrent access\nexists. The actual fix for #1878 was the Assert-to-runtime-check\nconversion and strndup defensive copy, which remain untouched."
    },
    {
      "commit": "639c8d5ac1cc930095caccb58b90dd65d9035e12",
      "tree": "e5dd18a05c182c063eb1bbec085f222b2fe7a5b5",
      "parents": [
        "73d0705e4ca7b3866b30436248b292b0170fb67b"
      ],
      "author": {
        "name": "Prashant Chinnam",
        "email": "5108573+crprashant@users.noreply.github.com",
        "time": "Mon Jun 08 16:55:50 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 08 16:55:50 2026 -0700"
      },
      "message": "Restore contsel/contjoinsel for containment \u0026 key-existence operators (#2356) (#2417)\n\nThe containment (`@\u003e`, `\u003c@`, `@\u003e\u003e`, `\u003c\u003c@`) and key-existence (`?`, `?|`,\n`?\u0026`) operators on `agtype` were bound to `matchingsel`/`matchingjoinsel`\non the PG14+ source tree. `matchingsel` is built for pattern operators\n(LIKE/regex) and during planning invokes the operator\u0027s underlying\nfunction (`agtype_contains`) once per `pg_statistic` MCV. With realistic\nstatistics targets that produces a planner-time regression that\ndominates simple OLTP-style point queries.\n\nRebind those operators to the lighter `contsel`/`contjoinsel` estimators,\nwhich return fixed selectivity constants without invoking the operator\nfunction during planning. This is a deliberate planning-speed vs.\nestimate-accuracy trade-off. Note it DIVERGES from PostgreSQL core, which\nkeeps jsonb\u0027s `@\u003e`, `\u003c@`, `?`, `?|`, `?\u0026` on `matchingsel`/`matchingjoinsel`\n(verified on REL_16/17/18_STABLE in `pg_operator.dat`); it is an\nAGE-specific choice favoring workloads where these operators appear in\nselective point lookups. A future improvement could add a custom `agtype`\nselectivity function that is both cheap and statistics-aware.\n\nChanges:\n\n* `sql/agtype_operators.sql`, `sql/agtype_exists.sql`: 10 operators\n  flipped from `matchingsel`/`matchingjoinsel` to\n  `contsel`/`contjoinsel`.\n* `age--1.7.0--y.y.y.sql`: appended `ALTER OPERATOR ... SET (RESTRICT,\n  JOIN)` for all 10 operators so existing installs flip on\n  `ALTER EXTENSION age UPDATE`.\n* `regress/sql/containment_selectivity.sql` (+ `expected/.out`): pin\n  the bindings via `pg_operator`, plus a scoped \"no leaked matchingsel\"\n  guard and functional smoke for all 10 operators. Adds an upgrade-path\n  assertion that simulates a stale (pre-fix) install, replays the\n  shipped `ALTER OPERATOR` block, and confirms every overload flips to\n  `contsel`/`contjoinsel` (run in a rolled-back transaction).\n* `regress/expected/cypher_match.out`, `regress/expected/cypher_vle.out`:\n  refresh expected to reflect new (and better) plan shapes that the\n  lower-selectivity helper produces — `test_enable_containment` now\n  picks Nested Loop + Index Only Scans over a Seq Scan/Hash Join, and\n  two `MATCH p\u003d...` and `show_list_use_vle` queries flip row order\n  (queries had no `ORDER BY`; result set is unchanged, only ordering).\n* `Makefile`: register `containment_selectivity` in `REGRESS`.\n\nValidation:\n\n* Build: clean, `-Werror`.\n* Regression: 36/37 tests pass under `EXTRA_TESTS\u003d\"pgvector\n  fuzzystrmatch pg_trgm\"`. Only `age_upgrade` fails — pre-existing on\n  master at 774e781b (verified by `git stash \u0026\u0026 installcheck`).\n* Reporter\u0027s exact methodology (LDBC-SNB-style snb_graph + pgbench on\n  `bench_message_content`) reproduces the regression and the fix:\n\n  | Metric                     | matchingsel | contsel | Delta |\n  |----------------------------|-------------|---------|-------|\n  | EXPLAIN planning time (ms) | 1.42        | 0.97    | -32%  |\n  | EXPLAIN execution time (ms)| 0.34        | 0.31    | ~equal|\n  | pgbench TPS (8c x 30s)     | 5247        | 7378    | +40.6%|\n\n  Run with `default_statistics_target \u003d 1000` to populate MCV lists,\n  matching the reporter\u0027s analyzed-graph conditions.\n\n* Upgrade path: validated end-to-end during the benchmark — operator\n  bindings were flipped from `matchingsel` -\u003e `contsel` via the same\n  `ALTER OPERATOR` statements the upgrade SQL ships, while operators\n  remained functional throughout.\n\nDriver workflows (python/go/node/jdbc) intentionally not run: this PR\nonly adjusts pg_operator selectivity metadata. There is no C, type, or\nprotocol change that drivers could observe.\n\nCloses #2356."
    },
    {
      "commit": "73d0705e4ca7b3866b30436248b292b0170fb67b",
      "tree": "24405ef6e5a95241ded4b8a4cbd09d1d017737d2",
      "parents": [
        "5a254d6869d8b2c271f025ea158c0fee2cfacfa3"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Wed Jun 03 01:08:54 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jun 03 13:08:54 2026 +0500"
      },
      "message": "perf: VLE hash-adjacency overhaul — agehash + flat-array VertexEdgeArray (#2421)\n\nReplaces the per-graph adjacency map with a Robin Hood open-addressing\nhashtable (agehash) and an embedded flat-array edge list, removing the\nhottest dynahash path on IC1 and shrinking the largest hashtable AGE\nkeeps. Stages land as one commit:\n\n  S1  MurmurHash3 fmix64 for graphid hashtables (replaces tag_hash)\n  S2  Precompute graphid hash; share across paired DFS lookups\n  S3  Replace ListGraphId adjacency with embedded flat-array\n      VertexEdgeArray (single palloc, contiguous iteration)\n  S4  Batched MLP lookup pipeline in add_valid_vertex_edges\n  S5/C1  agehash library: INLINE Robin Hood hashtable with\n         _with_hash API, freeze, iter, and a regress-only selftest\n  S5/C2  Wire global graph edge_hashtable through agehash;\n         drop edge_id from edge_entry (key lives in slot header);\n         AGEHASH_MAX_LOAD\u003d0.85; MemoryContextAllocHuge for SF10+\n\nPerformance (SF3 LDBC SNB, 5 runs/3 warmup, vs clean master baseline_v2):\n\n  IC1   8,625 → 7,117 ms   −17.49 %   (the headline; hashtable-bound)\n  IU1      40 →    35 ms   −11.86 %   (heaviest update; lookup-bound)\n  IC sum     198,958 → 197,367 ms     −0.80 %   (suite-level noise)\n  IS sum       1,009 →   1,028 ms     +1.86 %   (IS3 jitter; sub-ms)\n  IU sum          77 →      72 ms     −6.64 %\n  IC2/3/4/5/6/7/8/9/10/11/12: parity (within ±3.3 %, mostly ±1.5 %)\n\nThe VLE-DFS-heavy queries (IC3/5/6/9/11) sit at parity: with\nhash_search_with_hash_value at ≤1 % inclusive on their baseline\nflames, no hashtable swap can recover meaningful wall-time on them.\n\nMemory: removing edge_id from edge_entry saves ~416 MB on SF3 and\n~1.4 GB on SF10 for the global graph\u0027s edge_hashtable. Slot capacity\nuses MemoryContextAllocHuge so SF10+ edge tables can be built.\n\nAdds:\n  src/backend/utils/cache/agehash.c, src/include/utils/agehash.h\n  regress/sql/agehash.sql + expected/agehash.out (boundary selftest)\n  _agehash_self_test() in both fresh-install and upgrade SQL\n\nTested on PostgreSQL 18.3 (REL_18_STABLE): all 35 regression tests\npass (installcheck), warning-free build.\n\nmodified:   Makefile\nmodified:   age--1.7.0--y.y.y.sql\nnew file:   regress/expected/agehash.out\nnew file:   regress/sql/agehash.sql\nmodified:   sql/age_main.sql\nmodified:   src/backend/utils/adt/age_global_graph.c\nmodified:   src/backend/utils/adt/age_vle.c\nnew file:   src/backend/utils/cache/agehash.c\nmodified:   src/include/utils/age_global_graph.h\nnew file:   src/include/utils/agehash.h"
    },
    {
      "commit": "5a254d6869d8b2c271f025ea158c0fee2cfacfa3",
      "tree": "151854aaf8b8d876a098abcf41baf7fc02bd413f",
      "parents": [
        "795a881d20d6b7cf74439a8be06289ac3e390dc4"
      ],
      "author": {
        "name": "Sai Asish Y",
        "email": "say.apm35@gmail.com",
        "time": "Mon Jun 01 17:05:17 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 01 17:05:17 2026 -0700"
      },
      "message": "python driver: preserve trailing quotes in agtype string values (#2425)\n\n* python driver: preserve trailing quotes in agtype string values\n\nstr.strip(\u0027\"\u0027) in visitStringValue() and visitPair() removes every \u0027\"\u0027\non either side of the token, not just the outer delimiters, so a value\nending in an escaped quote (e.g. \u0027\"foo \\\"bar\\\"\"\u0027) loses its trailing\nbackslash-escaped \u0027\"\u0027 character.  The Agtype grammar guarantees STRING\ntokens always carry exactly one delimiter on each side, so slice with\n[1:-1] to strip them precisely.\n\nFixes #2418\n\nSigned-off-by: SAY-5 \u003csaiasish.cnp@gmail.com\u003e\n\n* fix(python-driver): centralize string delimiter trim and clean test docstring\n\n---------\n\nSigned-off-by: SAY-5 \u003csaiasish.cnp@gmail.com\u003e"
    },
    {
      "commit": "795a881d20d6b7cf74439a8be06289ac3e390dc4",
      "tree": "104abbd13451028a5f53b8e0823b932eb6d05c14",
      "parents": [
        "5ef7d6d55cbf73b359604ff816a8f86580f94b7f"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Mon Jun 01 11:52:20 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 01 11:52:20 2026 -0700"
      },
      "message": "perf(agtype): arena passthrough + binary-direct id extraction (#2424)\n\nTargets the agtype value-tree allocation cost that dominates LDBC IC4 and\nother sort/comparator-bound queries. Two complementary optimizations\n(A1 arena, A2 fast-id) plus the sanitizer issues uncovered while\nverifying the change.\n\nA1 — per-tuple agtype arena (Pattern B applied):\n  Add agt_arena_begin / AGT_ARENA_BEGIN_SHARED / agt_arena_reset /\n  agt_arena_end helpers (agtype_util.c, agtype.h). Two usage patterns:\n  disposable per-call arenas for one-shot tree builds, and a long-lived\n  shared arena (file-static, parented under CacheMemoryContext) for hot\n  inner loops that would otherwise pay AllocSetCreate cost per call.\n\n  Apply to compare_agtype_scalar_containers: when the comparator\n  deserializes VERTEX/EDGE/PATH composites via fill_agtype_value_no_copy,\n  route those allocations into a shared arena and reset (O(1)) at the\n  end of the comparison instead of recursively pfree\u0027ing the tree (O(N)).\n  Master IC4 spent 17.64% of total CPU in pfree_agtype_value_content from\n  this comparator; the arena collapses that walk to one MemoryContextReset.\n\nA2 — binary-direct id extraction:\n  When compare_agtype_scalar_containers compares two VERTEX or two EDGE\n  values, only the graphid \u0027id\u0027 determines order (compare_agtype_scalar_values\n  ignores all other fields). Master IC4 spent 73.48% inclusive in\n  ag_deserialize_composite called from this comparator, building the entire\n  agtype_value tree (label, properties, nested values) just to read one int64.\n\n  Add extract_composite_id_fast: walks the binary VERTEX/EDGE container\n  directly and reads the int64 id at field index 0 (always \u0027id\u0027 due to\n  length-sorted key ordering established by uniqueify_agtype_object — the\n  same invariant PR #2302 leveraged for direct array indexing).\n\n  Wired into compare_agtype_scalar_containers as a fast path before the\n  existing arena+slow path. Falls through on cross-type comparisons\n  (VERTEX vs EDGE), PATH, and malformed extended-type entries.\n\nASAN/UBSan fixes (10 issues found while verifying A1+A2 under\n-fsanitize\u003daddress -fsanitize\u003dundefined; 1 introduced by A2, 9 pre-existing).\nThe 11th finding (uninitialized parent_cpstate fields in cypher_analyze.c)\nwas independently fixed upstream by PR #2423; this branch is rebased on\ntop of that fix and no longer carries a duplicate change.\n  - HIGH: heap-buffer-overflow in agtype_raw.c:write_container — VARSIZE\n    included the 4-byte varlena header but copy started past it, reading\n    VARHDRSZ bytes past source allocation. Subtract VARHDRSZ. ASan flagged\n    4 times in installcheck.\n  - 7 unaligned int64/float8 reads/writes (AGT_HEADER is 4 bytes, leaving\n    payload 4-byte- but not 8-byte-aligned). UB under strict-alignment\n    rules. Replaced typed loads/stores with memcpy in agtype_ext.c (4 sites),\n    agtype_util.c (3 sites incl. extract_composite_id_fast), agtype_raw.c\n    (write_graphid).\n  - agtype.c:agtype_hash_cmp: reading r-\u003eval.array.raw_scalar at\n    WAGT_END_ARRAY where the iterator does not populate *val. Replaced\n    with explicit raw_scalar bool stack tracked across BEGIN/END pairs.\n    Hash output unchanged.\n  - agtype_util.c:copy_to_buffer: memcpy(NULL, NULL, 0) is UB. Guard with\n    if (len \u003e 0).\n\nPerformance (SF3 LDBC, paired same-session vs master):\n  IC sum:   201,930 -\u003e 157,129 ms \u003d -22.19%\n  IC4:       50,570 -\u003e   7,615 ms \u003d -84.94%\n  IC5:       21,779 -\u003e  20,646 ms \u003d  -5.20%\n  IS, IU:   within noise (\u003c\u003d +/-3% on sub-second queries)\n\nTests:\n  34/34 installcheck on production PG18.3\n  34/34 installcheck on ASAN+UBSan PG18.3 (zero unique runtime errors,\n        zero heap-buffer-overflows; the original audit found 10 unique\n        runtime issues plus 1 HBO with 4 instances, all addressed here\n        or upstream)\n\nCo-authored-by: Claude noreply@anthropic.com\n\nmodified:   src/backend/utils/adt/agtype.c\nmodified:   src/backend/utils/adt/agtype_ext.c\nmodified:   src/backend/utils/adt/agtype_raw.c\nmodified:   src/backend/utils/adt/agtype_util.c\nmodified:   src/include/utils/agtype.h"
    },
    {
      "commit": "5ef7d6d55cbf73b359604ff816a8f86580f94b7f",
      "tree": "6900c406d3fbd54ed2178772e41200b63ae48fff",
      "parents": [
        "f02eda0e0504bf3e941dd4a05e3060d2f3bb4f04"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Mon Jun 01 23:02:07 2026 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 01 11:02:07 2026 -0700"
      },
      "message": "Add vertex and edge composite types with direct field access optimization (#2303)\n\n- Introduce vertex and edge as pg composite types\n  vertex: (id, label, properties)\n  edge: (id, label, start_id, end_id, properties)\n- end_id precedes start_id intentionally, it matches the agtype edge\u0027s\n  canonical key order (keys sorted by length, end_id\u003d6 \u003c start_id\u003d8),\n  keeping the composite positionally consistent with the agtype\n  representation.\n- Property access (a.name) now directly uses a.properties for\n  agtype_access_operator instead of rebuilding via _agtype_build_vertex/edge\n- Optimize accessor functions (id, properties, label, type, start_id, end_id)\n  to use direct FieldSelect on composite types instead of agtype functions\n- Add casts: vertex/edge to agtype, vertex/edge to json/jsonb\n- Add eq and not eq ops for vertex/edge composite types.\n- Fix label_name specific routine to use cache instead of ag_label scan\n- Write/update clauses have executors strictly tied to agtype, due to which\n  the variables after any write/update clause are carried forward as agtype.\n- Allows users to completely skip agtype build functions and return vertex/edge\n  for pure read queries.\n- Change _label_name to return agtype since record comparisons are not\n  allowed with cstring. Consequently, _agtype_build_vertex/edge now accept\n  agtype as label.\n- Fix MERGE clause type mismatch when accessing properties from previous\n  MATCH clauses by wrapping columns with agtype_volatile_wrapper\n  before namespace lookup.\n- Update expression index in pgvector.sql, since now it uses raw properties column\n  instead of _agtype_build_vertex/edge.\n- Add regression tests\n\nAssisted-by AI"
    },
    {
      "commit": "f02eda0e0504bf3e941dd4a05e3060d2f3bb4f04",
      "tree": "b015f484b424ae82055b61ee4e051036e962cf86",
      "parents": [
        "9960e9c6034ed301b90e6e6777c80b7c95b45cf0"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Mon Jun 01 09:05:49 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 01 21:05:49 2026 +0500"
      },
      "message": "perf: VLE terminal-qual rewrite (#2420)\n\nperf: VLE terminal-qual rewrite — emit endpoint equalities instead of SRF\nqual functions.\n\nRemoves the per-row age_match_vle_terminal_edge and age_match_two_vle_edges\nqual functions from VLE query plans. The cypher transformer now emits the\nendpoint match as a plain graphid/int8 equality on new SRF output columns,\nevaluated by the planner like any other join clause — no detoasting, no\nper-row C function dispatch. Stages land as one commit:\n\n  S1  Inline start_vid/end_vid in VLE_path_container header\n  S2  Read VLE qual endpoints from header-only TOAST slice\n  S4  Emit start_id/end_id as scalar SRF output columns\n      (age_vle now RETURNS SETOF record with edges/start_id/end_id)\n  S5  Cypher transformer rewrites terminal-edge match quals as\n      integer equalities (drops age_match_vle_terminal_edge call)\n  S6  Cypher transformer emits graphid equality for two-VLE-edge\n      joins (drops age_match_two_vle_edges call)\n\nPerformance (SF3 LDBC SNB, 5 runs/3 warmup, vs clean master baseline_v2):\n\n  IC sum    198,958 → 109,322 ms   −45.05 %  (1.82× end-to-end speedup)\n  IC1   8,625 →  4,600 ms  −46.67 %\n  IC3  21,239 →  9,784 ms  −53.93 %\n  IC5  21,051 →  5,696 ms  −72.94 %\n  IC6  15,916 →  4,447 ms  −72.06 %\n  IC9  44,839 → 21,161 ms  −52.81 %\n  IC10 13,104 →  2,432 ms  −81.44 %\n  IC11 11,676 →    241 ms  −97.93 %  (48× speedup)\n  IC2/4/7/8/12: parity (within ±3.3 %; IC4 is −2.47 %, no regression)\n  IS sum:  1,009 → 1,004 ms   −0.51 %  (no VLE traffic)\n  IU sum:     77 →    71 ms   −8.38 %  (IU1 −16.09 %; incidental)\n\nMemory: header-only TOAST slice for VLE qual evaluation avoids\ndetoasting full path containers on every row; reduces per-call\npalloc/pfree churn in long DFS paths. No measured RSS change.\n\nDead-code removal:\n  - Bodies of age_match_vle_terminal_edge and age_match_two_vle_edges\n    are gone from age_vle.c (~225 lines).  C entry points remain as\n    error-raising stubs solely so the upgrade-test snapshot loader\n    (which sources an older 1.7.0_initial SQL against the current\n    age.so) can resolve the symbols before the immediate ALTER\n    EXTENSION UPDATE drops them.  No regress test references either\n    function.\n  - SQL CREATE FUNCTION declarations removed from fresh install\n    (sql/agtype_typecast.sql).\n  - DROP FUNCTION IF EXISTS for both qual functions added to the\n    upgrade script (age--1.7.0--y.y.y.sql).\n\nAPI change: ag_catalog.age_vle(...) now RETURNS SETOF record with\noutput columns (edges agtype, start_id graphid, end_id graphid)\ninstead of RETURNS SETOF agtype.  Both 7-arg and 8-arg overloads\nare updated in fresh-install (sql/agtype_typecast.sql) and upgrade\n(age--1.7.0--y.y.y.sql) paths.  age_match_vle_terminal_edge and\nage_match_two_vle_edges are dropped on upgrade and absent from\nfresh installs.  Internal AGE callers are unaffected; external SQL\nthat called any of these directly must adapt.\n\nHardening (in response to PR #2420 review feedback):\n  - cypher_clause.c: terminal-edge and two-VLE-edge join-qual\n    emission paths now bracket the existing Assert(vle_alias !\u003d NULL)\n    with a runtime ereport(ERROR, ...) so a missing alias produces a\n    clean error in production builds (where Asserts compile out)\n    instead of a NULL-deref crash inside makeString().\n  - age_vle.c: VLE_path_container struct comment now documents that\n    the layout is transient (consumed within the producing query and\n    never persisted), so the new start_vid/end_vid fields do not\n    require an AGT_FBINARY_TYPE_VLE_PATH version bump or backward-\n    compatible reader. The note also flags the constraint a future\n    change would need to honor if the container ever became\n    persistable.\n\nTested on PostgreSQL 18.3 (REL_18_STABLE): all 34 regression tests\npass (installcheck), warning-free build.\n\nmodified:   age--1.7.0--y.y.y.sql\nmodified:   regress/expected/cypher_match.out\nmodified:   regress/expected/cypher_vle.out\nmodified:   regress/expected/expr.out\nmodified:   sql/agtype_typecast.sql\nmodified:   src/backend/parser/cypher_clause.c\nmodified:   src/backend/parser/cypher_transform_entity.c\nmodified:   src/backend/utils/adt/age_vle.c\nmodified:   src/include/parser/cypher_transform_entity.h"
    },
    {
      "commit": "9960e9c6034ed301b90e6e6777c80b7c95b45cf0",
      "tree": "d1c1e0c89bd8112dbe839328cd2f935ffde722d4",
      "parents": [
        "01ee9414f9b483782cbf50cfe0744860bcf375ae"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Wed May 13 20:02:14 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed May 13 17:02:14 2026 -0700"
      },
      "message": "Add agtype \u003c-\u003e jsonb bidirectional casts (#350) (#2361)\n\n* Add agtype \u003c-\u003e jsonb bidirectional casts (issue #350)\n\nRegister explicit casts between agtype and jsonb, enabling:\n\n  SELECT properties(n)::jsonb FROM cypher(...) AS (n agtype);\n  SELECT \u0027{\"key\": \"value\"}\u0027::jsonb::agtype;\n\n  -- Use jsonb operators on graph data:\n  SELECT (props::jsonb)-\u003e\u003e\u0027name\u0027 FROM cypher(...) AS (props agtype);\n\nImplementation uses SQL language functions that go through proven\ntext-intermediate paths:\n  agtype -\u003e jsonb:  agtype_to_json() -\u003e json::jsonb\n  jsonb -\u003e agtype:  jsonb::text -\u003e text::agtype\n\nThis approach is safe because agtype extends jsonb\u0027s binary format\nwith types (AGTV_INTEGER, AGTV_FLOAT, AGTV_VERTEX, AGTV_EDGE,\nAGTV_PATH) that jsonb does not recognize, making direct binary\nconversion unreliable.  The text roundtrip handles all value types\ncorrectly including graph types (vertex/edge properties are\nextracted as JSON objects).\n\nAll 31 regression tests pass.\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Address Copilot review: fix comment, add regression tests for jsonb casts\n\n- Fix comment in agtype_coercions.sql: document json-intermediate path\n  (agtype_to_json -\u003e json::jsonb) instead of incorrect \"text intermediate\"\n- Add agtype_jsonb_cast regression test covering: string/null/array/object\n  agtype-\u003ejsonb, all jsonb scalar types-\u003eagtype, roundtrips, vertex/edge\n  -\u003ejsonb with structural key checks, NULL handling\n- Register agtype_jsonb_cast in Makefile REGRESS list\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Register agtype \u003c-\u003e jsonb casts in upgrade template\n\nThe age_upgrade regression test (added after this PR was originally\nopened) verifies that every SQL object in the install SQL also appears\nin the version upgrade template (age--\u003cINIT\u003e--\u003cCURR\u003e.sql).  Surfaced\nduring rebase onto current master: agtype_to_jsonb / jsonb_to_agtype\nand their casts were missing from the template, causing 4 entries in\nthe \"missing_function\" / \"missing_cast\" check.\n\nAdds the two functions and two casts to the end of the template,\nfollowing the file\u0027s own \"add all additions to the end of this file\"\nconvention.  Cassert installcheck now 34/34 AGE tests green (pgvector\nskipped — env-only).\n\n---------\n\nCo-authored-by: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "01ee9414f9b483782cbf50cfe0744860bcf375ae",
      "tree": "3478b54fda1a562a31aa20172844182e2fb834f4",
      "parents": [
        "a1b749af383dfdf0958ba6c1531b99aeab9207f5"
      ],
      "author": {
        "name": "Ueslei Lima",
        "email": "uesleisantoslima@gmail.com",
        "time": "Wed May 13 12:16:14 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed May 13 08:16:14 2026 -0700"
      },
      "message": "feat(python-driver): add public API for connection pooling and model dict conversion (#2374)\n\n* feat(python-driver): add public API for connection pooling and model dict conversion\n\nAdd two enhancements to the Python driver\u0027s public API:\n\n1. Add configure_connection() function that registers AGE agtype adapters\n   on an existing psycopg connection without creating a new one. This\n   enables use with external connection pools (e.g. psycopg_pool) and\n   managed PostgreSQL services where LOAD \u0027age\u0027 may be restricted.\n   Also explicitly export AgeLoader and ClientCursor as public symbols\n   in age/__init__.py. (#2369)\n\n2. Add to_dict() methods to Vertex, Edge, and Path model classes for\n   conversion to plain Python dicts. This enables direct JSON\n   serialization with json.dumps() without requiring custom conversion\n   logic. (#2371)\n\n   - Vertex.to_dict() returns {id, label, properties}\n   - Edge.to_dict() returns {id, label, start_id, end_id, properties}\n   - Path.to_dict() returns a list of to_dict() results\n\nCloses #2369\nCloses #2371\n\n* Fix configure_connection: correct parameter semantics, add load_from_plugins\n\n- Replace confusing `skip_load` (double-negative) with `load` (positive\n  boolean, default False). The default now correctly matches the intent:\n  no LOAD by default for connection pool / managed PostgreSQL use cases.\n- Add `load_from_plugins` parameter for parity with setUpAge().\n- Fix docstring to accurately describe parameter behavior.\n- Add 6 unit tests for configure_connection covering: default no-load,\n  explicit load, load_from_plugins, search_path always set, adapter\n  registration, and graph_name check delegation.\n\nMade-with: Cursor\n\n* Address review feedback for configure_connection and to_dict\n\n- Move TypeInfo.fetch() inside cursor block so search_path change is\n  visible regardless of transaction isolation mode\n- Raise ValueError when load_from_plugins\u003dTrue but load\u003dFalse\n- Add type annotations to configure_connection signature\n- Document shallow-copy semantics in Vertex/Edge to_dict()\n- Path.to_dict() uses str() fallback for non-AGObj entities to\n  guarantee JSON-serializable output\n- Add test for AgeNotSet when TypeInfo.fetch returns None\n- Add test for load_from_plugins\u003dTrue without load\u003dTrue\n- Replace fragile string assertions with assert_called_with/assert_any_call\n\nMade-with: Cursor\n\n* Fix Path.to_dict() to preserve JSON-native types, add tests to suite\n\n- Path.to_dict(): leave dict/list/str/int/float/bool/None unchanged\n  instead of converting to str(); handle entities\u003dNone safely\n- Add TestModelToDict, TestPublicImports, TestConfigureConnection to\n  the __main__ suite so they run via direct script execution\n\nMade-with: Cursor\n\n* fix(python-driver): Python 3.9-safe hints and correct $user in search_path\n\n- Use Optional[str] for configure_connection graph_name (PEP 604 unions are\n  invalid on Python 3.9).\n- Import Any/Optional from typing for annotations.\n- Quote $user in SET search_path; align unit test expectations.\n\nMade-with: Cursor\n\n* test(python-driver): add configure_connection + to_dict integration test\n\nExisting tests for the new public API are unit-only:\n- TestConfigureConnection mocks the psycopg connection, so it never\n  proves that AgeLoader actually registers against real agtype OIDs.\n- TestModelToDict hand-constructs Vertex/Edge/Path via kwargs, so it\n  never serialises objects produced by the ANTLR parser.\n\nAdd a single TestAgeBasic.testConfigureConnection that:\n- opens a raw psycopg connection (bypassing age.connect()),\n- calls configure_connection(..., load\u003dTrue) on it,\n- runs a Cypher CREATE/RETURN through the configured connection,\n- asserts the returned values are real Vertex/Edge instances and that\n  their to_dict() output is JSON-serialisable with the expected\n  label/start_id/end_id/properties shape,\n- repeats the round-trip for a Path returned by MATCH.\n\nThis is the smallest test that proves the configure_connection +\nto_dict pipeline works end-to-end against a live AGE database.\n\nMade-with: Cursor"
    },
    {
      "commit": "a1b749af383dfdf0958ba6c1531b99aeab9207f5",
      "tree": "b83ca631fbcb0a16389f84427d6e8a42e9a8653b",
      "parents": [
        "e9ef30b177040662a79dcb55307ca3b1a5cf7582"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Tue May 05 14:02:19 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue May 05 23:02:19 2026 +0200"
      },
      "message": "Fix upgrade test: allow function removal (#2422)\n\nFix upgrade test: allow function removal and detect more deficiencies.\n\nThe age_upgrade regression test (added in #2364, refined in #2377, #2397,\ninstall and a synthetic-initial -\u003e current upgrade. Three gaps surfaced\nin practice:\n\n1. Function removal forced permanent C stubs.\n   The synthetic \u0027_initial\u0027 install is built from a fixed historical\n   commit. CREATE EXTENSION resolves every CREATE FUNCTION ... AS\n   \u0027$libdir/age\u0027, \u0027\u003csymbol\u003e\u0027 via dlsym at install time when\n   check_function_bodies is on (the default). If a developer retires a\n   C entry point in HEAD\u0027s age.so, step 10 aborts with \"could not find\n   function ... in file age.so\" -- even though the immediately-following\n   ALTER EXTENSION UPDATE would DROP that SQL declaration. The only way\n   to keep the test green was to leave a permanent error-raising stub\n   in age.so, and to remember to add a DROP to the upgrade template.\n\n2. Modifications were under-detected.\n   The function-property-change query did not compare probin or prosrc,\n   so a C function whose symbol was renamed in the upgrade template, or\n   a SQL/plpgsql function whose body changed in either path, slipped\n   through.\n\n3. Extension membership was not checked.\n   A template that CREATEs an object but never ALTER EXTENSION ADDs it\n   leaves a row in pg_proc/pg_class but no pg_depend deptype\u003d\u0027e\u0027 link.\n   pg_dump --extension would diverge, but the existing per-catalog diff\n   queries all returned 0 rows.\n\nChanges (regress/sql/age_upgrade.sql + regress/expected/age_upgrade.out):\n\n* Step 10 wraps the synthetic CREATE EXTENSION in\n  SET check_function_bodies \u003d off; ... RESET check_function_bodies;\n  Symbol resolution is deferred to call time. Step 11\u0027s ALTER EXTENSION\n  UPDATE then DROPs any retired functions before any plan can call them.\n  Step 35\u0027s fresh CREATE EXTENSION runs at the GUC default, so HEAD\u0027s\n  sql/ \u003c-\u003e HEAD\u0027s age.so consistency is still enforced on the production\n  install path.\n\n* Steps 2 and 13 add probin and prosrc to the function snapshot.\n  Step 21 reports probin and prosrc divergences alongside the existing\n  property-change columns.\n\n* Steps 7b and 18b add an extension-membership snapshot from\n  pg_depend deptype\u003d\u0027e\u0027 filtered to the AGE extension OID. Every member\n  is labeled by stable identity (regprocedure, regtype, regoperator,\n  opfname+strategy+types, etc.), never by raw OID, so OID drift between\n  fresh and upgrade installs cannot produce false positives. Steps 33a\n  and 33b report MISSING / EXTRA members. Step 34 adds extmembers_match\n  to the summary row.\n\n* Section-header step ranges updated to include the new sub-steps.\n\nThe change is fully self-contained: only regress/sql/age_upgrade.sql and\nregress/expected/age_upgrade.out are modified. No production C, SQL,\nbuild, or test files are touched. All 34 regression tests pass on a\nclean tree.\n\nMutation-tested with 8 cases against the unmutated tree: baseline pass;\nremove-function-with-DROP pass (no stub needed); remove-function-forget-\nDROP fail; add-function-with-CREATE pass; add-function-forget-CREATE\nfail; volatility-change-no-template fail; volatility-change-with-CREATE-\nOR-REPLACE pass; C-symbol-rename-no-template fail. All eight expected\noutcomes observed.\n\nAll 34 regression tests pass.\n\nCo-authored-by: Claude \u003cnoreply@anthropic.com\u003e\n\nmodified:   regress/expected/age_upgrade.out\nmodified:   regress/sql/age_upgrade.sql"
    },
    {
      "commit": "e9ef30b177040662a79dcb55307ca3b1a5cf7582",
      "tree": "52c3eb125437277704c13c87ec3caa2739bbcca4",
      "parents": [
        "54e19fac9f257ecd173e3dcf647c990e74a28323"
      ],
      "author": {
        "name": "Hari Krishna Sunder",
        "email": "hari90@users.noreply.github.com",
        "time": "Mon May 04 11:23:25 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon May 04 11:23:25 2026 -0700"
      },
      "message": "Zero-initialize parent_cpstate in analyze_cypher (#2423)\n\ncypher_parsestate parent_cpstate is declared on the stack in\nanalyze_cypher() and only pstate is explicitly set before it is passed\nto make_cypher_parsestate(). The latter reads\nparent_cpstate-\u003esubquery_where_flag (and other fields) in\ncypher_parse_node.c, which leaves them with indeterminate values. UBSan\nflagged the garbage bool (value 8) and aborted the backend.\n\nUse MemSet to zero the struct before populating pstate so all remaining\nmembers start with a defined value.\n\nCo-authored-by: Hari Krishna Sunder \u003c12418230+hari90@users.noreply.github.com\u003e"
    },
    {
      "commit": "54e19fac9f257ecd173e3dcf647c990e74a28323",
      "tree": "6b08021af72b952ab1fadfb1f69086c06bea5529",
      "parents": [
        "ff47828b9f1ba474647923bdb0b73299a96a3a79"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Thu Apr 30 00:16:31 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Apr 30 12:16:31 2026 +0500"
      },
      "message": "Fix upgrade test: replace data-integrity checks with catalog comparison (#2403)\n\nThe age_upgrade regression test (added in #2364, improved in #2377, #2397)\nwas designed to validate the upgrade template (age--\u003cVER\u003e--y.y.y.sql) by\ncreating graph data before the upgrade and verifying it survived afterward.\nThis approach had two fundamental problems:\n\n1. It did not detect incomplete upgrade templates. The test verified that\n   graph data (vertices, edges, checksums, GIN indexes) survived ALTER\n   EXTENSION UPDATE, but never checked whether new SQL objects (functions,\n   views, relations, indexes, types, operators, casts, constraints) were\n   actually created by the template. A developer could add a new function\n   to sql/ and sql_files, forget to add it to the upgrade template, and\n   all tests would pass — the function existed via the fresh CREATE\n   EXTENSION install that ran before the upgrade test, but would be\n   missing for users who upgraded via ALTER EXTENSION UPDATE.\n\n2. The data-integrity checks relied on cypher queries (MATCH/RETURN) within\n   the same backend session after DROP EXTENSION + CREATE EXTENSION. This\n   caused intermittent failures on some PostgreSQL versions where AGE\u0027s\n   internal type cache (agtype OID) was not properly refreshed after the\n   extension was dropped and recreated, resulting in \u0027type with OID 0\n   does not exist\u0027 errors. The data-integrity aspect was also redundant —\n   ALTER EXTENSION UPDATE runs DDL statements and does not touch heap data,\n   so data survival is guaranteed by PostgreSQL and not a meaningful test.\n\nThe fix replaces the entire test with a comprehensive catalog comparison:\n\n  1. Snapshot the ag_catalog schema from the fresh install across seven\n     PostgreSQL system catalogs:\n       - pg_proc: functions, aggregates, procedures (name, args, and\n         properties: volatility, strictness, kind, return type, setof)\n       - pg_class: tables, views, sequences, indexes (name, kind)\n       - pg_type: types (name, type category)\n       - pg_operator: operators (name, left/right operand types)\n       - pg_cast: casts involving AGE types (source, target, context)\n       - pg_opclass: operator classes (name, access method)\n       - pg_constraint: constraints (name, type, table, referenced table)\n  2. DROP EXTENSION, CREATE EXTENSION at the synthetic initial version,\n     then ALTER EXTENSION UPDATE to the current version via the stamped\n     upgrade template.\n  3. Snapshot the catalog again after upgrade.\n  4. Compare: any object present in the fresh snapshot but missing after\n     upgrade means the template is incomplete. Any object present after\n     upgrade but not in the fresh snapshot means the template creates\n     something unexpected. Function properties (volatility, strictness,\n     prokind, return type) are also compared for functions that exist in\n     both — catching cases where a CREATE OR REPLACE in the template\n     changes a function\u0027s signature or behavior.\n\nAdditional improvements from code review feedback:\n\n  - Graph cleanup in Step 1 uses a DO block with PERFORM and suppressed\n    NOTICEs to produce deterministic output regardless of prior test state.\n  - The pg_class snapshot includes indexes (relkind \u0027i\u0027) in addition to\n    tables, views, and sequences.\n  - Diagnostic output includes relkind/typtype suffixes for actionable diffs.\n  - Summary uses boolean equality checks (funcs_match, rels_match, etc.)\n    instead of absolute counts, so the expected output does not need\n    updating when new objects are added to AGE. Developers who correctly\n    add objects to both sql/ and the template will never need to modify\n    this test or its expected output.\n\nThis approach:\n  - Catches the actual failure mode: incomplete upgrade templates.\n  - Covers all SQL object categories: functions (including aggregates),\n    relations, types, operators, casts, operator classes, and constraints.\n  - Detects property changes on existing functions (volatility, strictness,\n    kind, return type changes).\n  - Uses only plain SQL catalog queries — no cypher, no .so cache issues.\n  - Works reliably across all PostgreSQL versions.\n  - Reports the exact missing/extra/changed object in the diff output.\n  - Is maintenance-free: no expected output changes needed when AGE grows.\n\nMakefile: updated step 5 comment to reflect catalog comparison approach.\n\nAll 33 regression tests pass.\n\nCo-authored-by: Claude \u003cnoreply@anthropic.com\u003e\n\nmodified:   Makefile\nmodified:   regress/expected/age_upgrade.out\nmodified:   regress/sql/age_upgrade.sql"
    },
    {
      "commit": "ff47828b9f1ba474647923bdb0b73299a96a3a79",
      "tree": "8ed5362a471eebd6a533317898e7f6ce4594853a",
      "parents": [
        "774e781b43878fadf829e185be202dd51eb6def8"
      ],
      "author": {
        "name": "Prashant Chinnam",
        "email": "5108573+crprashant@users.noreply.github.com",
        "time": "Wed Apr 29 15:13:45 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 29 15:13:45 2026 -0700"
      },
      "message": "Allow safe Cypher reserved keywords in alias positions (#2355) (#2415)\n\nCypher productions in `cypher_gram.y` that bind an alias via the AS\nkeyword (RETURN/WITH/YIELD ... AS x and UNWIND ... AS x) only accepted\nplain identifiers. As a result, completely valid Cypher such as\n\n    SELECT * FROM cypher(\u0027g\u0027, $$ RETURN 1 AS count $$) AS (a agtype);\n\nfailed with `syntax error at or near \"count\"`, even though `count` is\nalready accepted in other identifier positions (it appears in the\nexisting `safe_keywords` list and is permitted in `func_name`;\n`schema_name` accepts the broader `reserved_keyword` set).\n\nThis patch introduces a dedicated `var_name_alias` non-terminal used\nonly in the three alias-binding sites (yield_item, return_item,\nunwind). It accepts everything `var_name` accepts, plus the entire\n`safe_keywords` set, so the 49 non-conflicting reserved keywords\n(count, exists, coalesce, match, return, where, order, limit, distinct,\noptional, detach, contains, starts, ends, in, is, not, ...) are now\nusable as aliases.\n\nThe change is intentionally scoped to alias positions:\n\n  * `var_name` itself (used by pattern-variable bindings like\n    `(x:Label)`, edge bindings, named paths, and `expr_var` references)\n    is unchanged. Allowing safe_keywords there triggers 156 shift/reduce\n    conflicts because keyword tokens collide with their roles inside\n    expressions and patterns.\n  * `conflicted_keywords` (END, NULL, TRUE, FALSE) remain rejected in\n    every position; they are genuinely ambiguous with literal/CASE\n    productions.\n\nReading a keyword-named alias back through `expr_var` still fails (e.g.\n`WITH 1 AS count RETURN count`) because `expr_var` reads through\n`var_name`. That asymmetry is captured as a known limitation in the\nregression suite and tracked separately in #2416.\n\nRegression coverage lives in `regress/sql/reserved_keyword_alias.sql`\nand `regress/expected/reserved_keyword_alias.out`, exercising:\n\n  * the original repro,\n  * representative safe_keywords across RETURN/WITH/UNWIND,\n  * multiple keyword aliases in one projection,\n  * a backtick-quoted alias positive case,\n  * the known read-back limitation as a negative test, and\n  * explicit negatives proving END/NULL/TRUE/FALSE and pattern-position\n    keywords still error out.\n\nCloses #2355."
    },
    {
      "commit": "774e781b43878fadf829e185be202dd51eb6def8",
      "tree": "25d4325c5b9ba470b392db3fb5eb811077fb774b",
      "parents": [
        "22f4c94fb821376d9b4de5b31dc3240225c187fa"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Mon Apr 27 23:35:00 2026 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 27 11:35:00 2026 -0700"
      },
      "message": "Propagate null through agtype arithmetic operators (#2405)\n\nThe scalar branch of agtype_add lacked an AGTV_NULL case and fell through\nto the scalar-scalar concat path, so `null + 1` produced the two-element\nlist `[null, 1]` instead of null. This surfaced most visibly inside a\nlist comprehension projection (e.g. `[x IN [1, null, 2] | x + 1]` yielded\n`[2, [null, 1], 3]`) because there both operands are typed agtype and\nbypass the agtype_any_add wrapper that already short-circuits on null.\n\nThe sibling operators (-, *, /, %, ^, unary -) had the same gap but\nsurfaced as \"Invalid input parameter types\" errors rather than concats.\n\nShort-circuit all seven functions to SQL NULL when any operand is\nAGTV_NULL. SQL NULL (not a wrapped AGTV_NULL scalar) matches AGE\u0027s\nexisting convention: `RETURN null AS v` already yields SQL NULL at the\nrow level, and agtype_any_add already returns PG NULL for AGTV_NULL\ninput, so the two operator-resolution paths stay indistinguishable.\nList aggregation re-packs SQL NULL entries as AGTV_NULL, so\n`[x IN [1, null, 2] | x + 1]` now renders as `[2, null, 3]`.\n\nCo-authored-by: Claude Opus 4.7 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "22f4c94fb821376d9b4de5b31dc3240225c187fa",
      "tree": "ff379a39906ca36ba6f4a8c38bfa73cf38f56911",
      "parents": [
        "9f9d0f3b18b231d229b17b3994a907aba7519695"
      ],
      "author": {
        "name": "Prashant Chinnam",
        "email": "5108573+crprashant@users.noreply.github.com",
        "time": "Mon Apr 27 11:32:27 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 27 11:32:27 2026 -0700"
      },
      "message": "Update README.md (#2414)\n\ndocs: Clarify transaction/commit semantics with non-autocommit clients\r\n\r\nAdds a \"Using AGE with Non-Autocommit Clients\" section explaining\r\nPostgreSQL transaction visibility rules as they apply to AGE DDL-like\r\nfunctions (create_graph, create_vlabel, etc.), with broken/fixed\r\npsycopg v3 examples and a JDBC note.\r\n\r\nRefs apache/age#2195"
    },
    {
      "commit": "9f9d0f3b18b231d229b17b3994a907aba7519695",
      "tree": "098de15a3e105f04ce4ba46ccdb60baa7eec9a9a",
      "parents": [
        "5a74048725ac6ddbe7bc15377c3a42cb0e31f22b"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Fri Apr 24 20:15:43 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Apr 24 17:15:43 2026 -0700"
      },
      "message": "Add VLE semantics and cost model design note (#2349) (#2413)\n\nAdds a block comment at the top of age_vle.c documenting (a) the\nedge-isomorphism semantics that openCypher mandates for variable-length\nrelationship matching, (b) the cost model distinction between bounded\nand unbounded patterns, and (c) a pointer to the enforcement site\n(edge_state_entry.used_in_path, dfs_find_a_path_*, is_edge_in_path).\n\nMotivation: issue #2349 misdiagnosed the VLE as lacking cycle detection\nand proposed a visited-node filter as a fix. Cycle prevention already\nexists via edge-uniqueness, and swapping to vertex-isomorphism would\nsilently drop spec-valid paths (e.g. triangle traversals where the\nendpoint coincides with the start vertex). Capturing the semantics and\ncost model in-source prevents future readers from repeating the same\nmisanalysis.\n\nNo behavior change. Cassert installcheck: 33/33."
    },
    {
      "commit": "5a74048725ac6ddbe7bc15377c3a42cb0e31f22b",
      "tree": "a1e356427a8b2a98e022f0b87ae8c55f1d3ffc8d",
      "parents": [
        "84e29542f0d54c9d848a3d5ebbc7a8f4d5a67437"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Sat Apr 25 05:10:31 2026 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Apr 24 17:10:31 2026 -0700"
      },
      "message": "Fix property access on list comprehension / predicate loop variables (#2402)\n\ntransform_column_ref_for_indirection errored with \"could not find\nproperties for \u003cname\u003e\" when the referenced RTE had no \"properties\"\ncolumn (as is the case for the unnest() RTE used by list comprehension\nand the any/all/none/single predicate functions), breaking queries like\n[x IN list | x.name] and any(x IN list WHERE x.n \u003e 1).\n\nReturn NULL instead so the caller continues transforming the ColumnRef\nas an agtype value and applies the indirection via agtype_access_operator.\n\nCo-authored-by: Claude Opus 4.7 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "84e29542f0d54c9d848a3d5ebbc7a8f4d5a67437",
      "tree": "671b855dbe739d201839b0de85e9b3aa4ccb0688",
      "parents": [
        "6f520fedda7885efcfc104b925338cdfa933f935"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Wed Apr 22 05:06:59 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 22 14:06:59 2026 +0500"
      },
      "message": "Add MERGE ON CREATE SET / ON MATCH SET support (#2347)\n\n- Add MERGE ON CREATE SET / ON MATCH SET support\n\nImplements the openCypher-standard ON CREATE SET and ON MATCH SET\nclauses for the MERGE statement. This allows conditional property\nupdates depending on whether MERGE created a new path or matched\nan existing one:\n\n  MERGE (n:Person {name: \u0027Alice\u0027})\n    ON CREATE SET n.created \u003d timestamp()\n    ON MATCH SET n.updated \u003d timestamp()\n\nImplementation spans parser, planner, and executor:\n\n- Grammar: new merge_actions_opt/merge_actions/merge_action rules\n  in cypher_gram.y, with ON keyword added to cypher_kwlist.h\n- Nodes: on_match/on_create lists on cypher_merge, corresponding\n  on_match_set_info/on_create_set_info on cypher_merge_information,\n  and prop_expr on cypher_update_item (all serialized through\n  copy/out/read funcs)\n- Transform: cypher_clause.c transforms ON SET items and stores\n  prop_expr for direct expression evaluation\n- Executor: cypher_set.c extracts apply_update_list() from\n  process_update_list(); cypher_merge.c calls it at all merge\n  decision points (simple merge, terminal, non-terminal with\n  eager buffering, and first-clause-with-followers paths)\n\nKey design choice: prop_expr stores the Expr* directly in\ncypher_update_item rather than using prop_position into the scan\ntuple. The planner strips target list entries for SET expressions\nthat CustomScan doesn\u0027t need, making prop_position references\ndangling. By storing the expression directly (only for MERGE ON\nSET items), we evaluate it with ExecInitExpr/ExecEvalExpr\nindependent of the scan tuple layout.\n\nIncludes regression tests covering: basic ON CREATE SET, basic\nON MATCH SET, combined ON CREATE + ON MATCH, multiple SET items,\nexpression evaluation, interaction with WITH clause, and edge\nproperty updates.\n\n- Move ExecInitExpr for ON CREATE/MATCH SET items from per-row\n  execution in apply_update_list() to plan initialization in\n  begin_cypher_merge(). Follows the established pattern used by\n  cypher_target_node (id_expr_state, prop_expr_state).\n- Add prop_expr_state field to cypher_update_item with serialization\n  support in outfuncs/readfuncs/copyfuncs.\n- apply_update_list() uses pre-initialized state when available,\n  falls back to per-row init for plain SET callers.\n- Fix misleading comment: \"ON MATCH SET\" → \"ON CREATE SET\" for Case 1\n  first-run test.\n- Add Case 1 second-run test that triggers ON MATCH SET with a\n  predecessor clause (MATCH ... MERGE ... ON MATCH SET).\n- Add ON to safe_keywords in cypher_gram.y so that property keys\n  and labels named \u0027on\u0027 still work (e.g., n.on, MATCH (n:on)).\n  All other keywords added as tokens are also in safe_keywords.\n- Add chained (non-terminal) MERGE regression tests exercising the\n  eager-buffering code path with ON CREATE SET and ON MATCH SET.\n  First run creates both nodes (ON CREATE SET fires), second run\n  matches both (ON MATCH SET fires).\n- Move ExecStoreVirtualTuple before apply_update_list unconditionally in\n  Case 1 non-terminal and terminal MERGE paths, matching the pattern at\n  Case 3 (line 994). Ensures tts_nvalid is set for downstream ExecProject\n  even when ON CREATE SET is absent.\n\n- Add resolve_merge_set_exprs() helper to deduplicate the prop_expr\n  resolution loops for ON MATCH SET and ON CREATE SET. Includes ereport\n  when target entry is missing (internal error, should never happen).\n\n- Add regression test for ON keyword as label name, confirming backward\n  compatibility via safe_keywords grammar path.\n- The four ExecStoreVirtualTuple calls in exec_cypher_merge were triggering\n  an Assert failure under --enable-cassert:\n\n    TRAP: failed Assert(\"TTS_EMPTY(slot)\"), File: execTuples.c, Line: 1748\n\nExecStoreVirtualTuple (execTuples.c:1748) asserts that its target slot\nis in the TTS_EMPTY state.  In our MERGE executor, process_path writes\ndirectly into the subquery\u0027s scan tuple slot -- which already holds the\nsubquery\u0027s output tuple and therefore is NOT empty.  On a release build\nthe assertion compiles out and ExecStoreVirtualTuple just clears the flag\nand sets tts_nvalid; on an --enable-cassert build the backend aborts and\ntakes down the regression run.\n\nWe only need the bookkeeping half of ExecStoreVirtualTuple (clear\nTTS_FLAG_EMPTY and set tts_nvalid \u003d natts) -- not the \"store semantics\"\nthat motivate the assertion.  Add a small static helper\nmark_scan_slot_valid() that does exactly the bookkeeping, and replace\nthe four call sites.  Release-build behavior is byte-identical since\nAssert() compiles to nothing; cassert-build behavior now matches release.\n\n- Fix MERGE ON CREATE/MATCH SET crash when RHS references a bound variable\n\nWhen MERGE has a previous clause (e.g. MATCH, UNWIND), transform_cypher_merge\ntakes the lateral-left-join path via transform_merge_make_lateral_join. That\nhelper called addRangeTableEntryForJoin with nscolumns\u003dNULL, leaving the join\nParseNamespaceItem\u0027s p_nscolumns unset. For queries that did not subsequently\nresolve a column reference against that nsitem (e.g. RETURN, which runs in a\nfresh namespace built by handle_prev_clause), the NULL was harmless.\n\nOur ON CREATE / ON MATCH SET transform runs in-line, before the MERGE query\nbecomes a subquery, so transform_cypher_set_item_list consulted the join\u0027s\nnsitem directly. colNameToVar -\u003e scanNSItemForColumn then dereferenced\np_nscolumns[attnum-1] \u003d NULL[0] and the backend segfaulted on any ON SET\nwhose RHS referenced a bound variable.\n\nPopulate the join\u0027s p_nscolumns from res_colvars. The Var we end up\nproducing for a bound entity lives inside prop_expr, which is opaque to\nthe planner, so it is not rewritten to match the plan\u0027s output slots. At\nExecEvalScalarVar time only varattno is consulted, and scantuple\u0027s layout\nmirrors the join\u0027s eref-\u003ecolnames (via make_target_list_from_join). Use\nthe join rtindex and 1-based eref position so scantuple[varattno - 1]\nresolves to the correct entity column at runtime; without this, Vars for\na (varno\u003dl_rte) and b (varno\u003dr_rte) with varattno\u003d1 both hit\nscantuple[0] and b.id evaluated to a.id.\n\nAlso initialise apply_update_list\u0027s new_property_value at its declaration.\nAll control paths reach the single alter_property_value call with the\nvariable set, but -Wmaybe-uninitialized fires at -O2 because the compiler\ncannot prove remove_property \u003d\u003d isnull when prop_expr is non-NULL.\n---------\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "6f520fedda7885efcfc104b925338cdfa933f935",
      "tree": "04cdb095afd6379058f284bea99b5ee4502ee614",
      "parents": [
        "798917c23e88b5297f5b8f1862418302e7953988"
      ],
      "author": {
        "name": "Sai Asish Y",
        "email": "say.apm35@gmail.com",
        "time": "Tue Apr 21 02:49:59 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Apr 21 14:49:59 2026 +0500"
      },
      "message": "Propagate null in agtype_access_slice bounds (#2400)\n\n* agtype: propagate null through list slice bounds in agtype_access_slice instead of treating AGTV_NULL as omitted\n\nagtype_access_slice() decides what to do with each bound in two steps:\n\n  1. If the SQL argument is NULL (`PG_ARGISNULL(1|2)`), the caller did\n     not supply that bound at all (e.g. `list[..2]`) - treat it as\n     \"no bound\" and fall back to 0 / array_size. This is correct.\n\n  2. Otherwise the argument is non-NULL agtype. If *the agtype value*\n     inside happens to be AGTV_NULL (e.g. `list[null..2]`), the\n     existing code also treated it as \"no bound\" and returned the full\n     slice. This is wrong. Under Cypher null-propagation semantics\n     `list[a..b]` is null whenever either bound is null; Neo4j and\n     Memgraph both return null, and differential testing against both\n     flagged the divergence.\n\nChange step 2 to PG_RETURN_NULL() when the supplied bound is\nAGTV_NULL. Step 1 is untouched, so `[..n]` / `[n..]` still work as\nbefore.\n\nRegression tests are updated accordingly:\n\n  * The two pre-existing agtype_access_slice() calls with an\n    explicit \u0027null\u0027::agtype argument now expect the null result.\n\n- The patch only fixes agtype_access_slice function, not direct\n  list slices in cypher query.\n\nCo-developed with an AI coding assistant (Claude Code).\nSigned-off-by: SAY-5 \u003cSAY-5@users.noreply.github.com\u003e"
    },
    {
      "commit": "798917c23e88b5297f5b8f1862418302e7953988",
      "tree": "f8b3ec998924f174809a86dd83d937c09b1d9274",
      "parents": [
        "ee25e2a381271d60d0fae9ff21a78f8a539bca3d"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Tue Apr 21 00:50:23 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Apr 21 12:50:23 2026 +0500"
      },
      "message": "VLE cache + performance improvements (#2376)\n\nVLE cache + perf: version counter, thin entries, variadic elimination,\nedge cleanup list removal\n\nReplace snapshot-based VLE cache invalidation with per-graph version\ncounters and add performance optimizations for VLE traversal.\n\nCache invalidation:\n- Add DSM (PG 17+) and shmem (PG \u003c17) per-graph monotonic version\n  counters for cross-backend cache invalidation\n- Replace snapshot xmin/xmax/curcid comparison with version counter\n  check in is_ggctx_invalid(), with snapshot fallback for safety\n- Add executor hooks in CREATE/DELETE/SET/MERGE to increment the\n  graph version counter on mutations\n- Add SQL trigger function (age_invalidate_graph_cache) for catching\n  SQL-level mutations (INSERT/UPDATE/DELETE/TRUNCATE)\n- Auto-install trigger on new label tables via label_commands.c\n  with LookupFuncName check for backward compatibility\n- Add TRUNCATE interception in ProcessUtility hook (ag_catalog.c)\n- Add shmem_request/startup hooks for PG \u003c17 (age.c)\n- Use search_label_relation_cache() in get_graph_oid_for_table()\n  for fast label-to-graph lookup instead of catalog table scan\n\nThin entries (lazy property fetch):\n- Replace Datum edge_properties/vertex_properties with 6-byte\n  ItemPointerData TID in vertex_entry and edge_entry\n- Add get_vertex_entry_properties() and get_edge_entry_properties()\n  that do heap_fetch via stored TID on demand\n- Add is_an_edge_match() fast path: skip property access entirely\n  for label-only VLE patterns (the common case)\n\nEdge cleanup list removal:\n- Remove ggctx-\u003eedges linked list from GRAPH_global_context. With\n  thin entries, edge_entry has no palloc\u0027d sub-structures to free\n  (TIDs are inline). The cleanup list existed to pfree datumCopy\u0027d\n  edge_properties Datums, which no longer exist. hash_destroy()\n  handles all edge hash table memory. Saves ~5.6 GB at SF10.\n\nPerformance optimizations:\n- Reduce VERTEX/EDGE_HTAB_INITIAL_SIZE from 1,000,000 to 10,000.\n  PG dynahash grows automatically; large initial size wastes memory.\n- Eliminate extract_variadic_args in age_match_vle_terminal_edge:\n  direct PG_GETARG_DATUM + cached arg types via fn_extra\n- Eliminate extract_variadic_args in age_match_vle_edge_to_id_qual:\n  same pattern\n- Add agtype_access_operator 2-arg fast path: bypasses\n  extract_variadic_args_min for the common property access case\n- Add age_tointeger 1-arg fast path: bypasses extract_variadic_args\n  with cached arg type\n- Add GraphIdStack (flat array-based) DFS stacks replacing\n  ListGraphId linked-list stacks for push/pop without palloc/pfree\n\nUpgrade script:\n- Add trigger installation on pre-existing label tables during\n  extension upgrade via DO block in age--1.7.0--y.y.y.sql. Tables\n  created after upgrade get triggers automatically via label_commands.c.\n\nRegression tests:\n- Add VLE cache invalidation tests (CREATE, DELETE, SET mutations)\n- Add thin entry edge property fetch tests (RETURN p, UNWIND)\n- Add direct SQL trigger tests (INSERT, UPDATE, DELETE, TRUNCATE on\n  label tables with VLE cache invalidation verification)\n\nAll 32 regression tests pass.\n\nSF3 Benchmarks (9.3M vertices, 52.7M edges, warm cache, median):\n\nTotal IC1-IC12: 1,530s -\u003e 1,159s (-24.3%, 371s saved)\n\nVLE-heavy queries:\n  IC3  (KNOWS*1..2 + 2 countries):    34.9s -\u003e 20.7s  (-40.6%)\n  IC5  (forum members, KNOWS*1..2):   46.2s -\u003e 29.9s  (-35.4%)\n  IC6  (tag co-occurrence, KNOWS*1..2): 28.2s -\u003e 19.2s (-31.8%)\n  IC9  (recent messages, KNOWS*1..2): 86.0s -\u003e 60.4s  (-29.7%)\n  IC11 (KNOWS*1..2 + WORK_AT):       18.7s -\u003e 11.0s  (-41.5%)\n  IC1  (KNOWS*1..3 + profile):     1269.4s -\u003e 974.9s  (-23.2%)\n\nShort Reads (IS1-IS7): No meaningful change -- non-VLE queries,\nsub-millisecond to sub-second. Within run-to-run variance.\n\nUpdates (IU1-IU8, SF3, median, 50 ops each):\n\n  IU2 (Like Post):     99.5ms -\u003e  6.3ms  (-93.6%)\n  IU3 (Like Comment): 318.3ms -\u003e  5.7ms  (-98.2%)\n  IU7 (Add Comment):  344.4ms -\u003e 11.3ms  (-96.7%)\n  IU5 (Forum Member):  12.3ms -\u003e  5.9ms  (-52.0%)\n\nVersion counter eliminates redundant VLE cache rebuilds on mutations.\nPreviously, every INSERT/UPDATE/DELETE invalidated the cache via\nsnapshot comparison, forcing a full rebuild on the next VLE query.\nNow, mutations just bump a counter; rebuild only occurs when VLE\nactually runs and finds the counter changed.\n\nGraph cache memory (SF3, calculated):\n\nTotal: ~15.7 GB -\u003e ~8.7 GB (-45%, 7.0 GB saved)\n\n  Thin entries (TID replaces datumCopy\u0027d properties):  -5.3 GB\n  Edge cleanup list removal (no longer needed):        -1.7 GB\n\nCo-authored-by: Claude Opus \u003cnoreply@anthropic.com\u003e\n\nmodified:   age--1.7.0--y.y.y.sql\nmodified:   regress/expected/age_global_graph.out\nmodified:   regress/sql/age_global_graph.sql\nmodified:   sql/age_main.sql\nmodified:   src/backend/age.c\nmodified:   src/backend/catalog/ag_catalog.c\nmodified:   src/backend/commands/label_commands.c\nmodified:   src/backend/executor/cypher_create.c\nmodified:   src/backend/executor/cypher_delete.c\nmodified:   src/backend/executor/cypher_merge.c\nmodified:   src/backend/executor/cypher_set.c\nmodified:   src/backend/utils/adt/age_global_graph.c\nmodified:   src/backend/utils/adt/age_graphid_ds.c\nmodified:   src/backend/utils/adt/age_vle.c\nmodified:   src/backend/utils/adt/agtype.c\nmodified:   src/include/utils/age_global_graph.h\nmodified:   src/include/utils/age_graphid_ds.h"
    },
    {
      "commit": "ee25e2a381271d60d0fae9ff21a78f8a539bca3d",
      "tree": "fff30bb506a30ecfe964d3a79d6a807d949c2744",
      "parents": [
        "26bd4f4d05e519cc340630bb71dc82e419485f31"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Mon Apr 20 20:31:39 2026 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 20 08:31:39 2026 -0700"
      },
      "message": "CI: fail build on compiler and bison warnings (#2398)\n\n- installcheck.yaml: build with COPT\u003d-Werror so compiler warnings fail CI\n- Makefile: add -Werror to BISONFLAGS for cypher_gram.c so bison\n  conflicts/warnings fail the build"
    },
    {
      "commit": "26bd4f4d05e519cc340630bb71dc82e419485f31",
      "tree": "3ef176a7e8c1a1d660977e680eb7f369759c83a2",
      "parents": [
        "908d7b29811a23d7b01c024af5d6697fe670e123"
      ],
      "author": {
        "name": "Ueslei Lima",
        "email": "uesleisantoslima@gmail.com",
        "time": "Mon Apr 20 07:41:03 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 20 15:41:03 2026 +0500"
      },
      "message": "fix(python-driver): add null-guards in ANTLR parser and relax runtime version pin (#2372)\n\nFix two related issues in the Python driver\u0027s ANTLR4 parsing pipeline:\n\n- Add null-guards in ResultVisitor methods (visitAgValue, visitFloatLiteral,\n  visitPair, visitObj, handleAnnotatedValue) to prevent AttributeError crashes\n  when the ANTLR4 parse tree contains None child nodes. This occurs with\n  vertices that have complex properties (large arrays, special characters,\n  deeply nested structures).\n\n- Relax antlr4-python3-runtime version constraint from \u003d\u003d4.11.1 to\n  \u003e\u003d4.11.1,\u003c5.0 in both pyproject.toml and requirements.txt. The 4.11.1\n  pin is incompatible with Python \u003e\u003d 3.13. The ANTLR ATN serialized format\n  is unchanged between 4.11 and 4.13, so the generated lexer/parser files\n  are compatible. Validated with antlr4-python3-runtime\u003d\u003d4.13.2 on\n  Python 3.11-3.14.\n\n- Also replaces shadowing of builtin \u0027dict\u0027 in handleAnnotatedValue with \u0027d\u0027,\n  and uses .get() for safer key access on parsed vertex/edge dicts.\n\n- Add tests for malformed/truncated agtype input handling. \n  Verify that malformed and truncated agtype strings raise AGTypeError\n  (or recover gracefully) rather than crashing with AttributeError.\n  This tests the null-guards added to the ANTLR parser visitor.\n\n- visitFloatLiteral: raise AGTypeError on malformed child node instead\n  of silently returning a fallback value\n\n- visitObj: add comment documenting that visitPair\u0027s validation makes\n  the None-guard defensive-only\n\n- handleAnnotatedValue: add comment explaining partial-construction\n  behavior on type-check failure\n\n- pyproject.toml: add comment explaining ANTLR4 version range rationale\n\n- Tests: assert AGTypeError (or graceful recovery) for malformed and\n  truncated inputs, not just absence of AttributeError\n\n- handleAnnotatedValue: default properties to {} when missing from\n  parsed dict, preventing __getitem__ crashes on access\n\n- Tests: replace weak assertNotIsInstance with structural type checks\n\n- Fix truncated test docstring to match actual assertion behavior\n\n- Use PostgreSQL \"$user\" placeholder in SET search_path.\n\n- Exercise real escapes and Unicode in special-characters vertex test (json.dumps).\n\n- Add Python 3.9 trove classifier to match requires-python and dependency comment.\n\n- Build vertex agtype with string concat to avoid invalid f-string braces.\n\n- Assert stored description matches parser behavior: JSON escapes remain\n  literal, UTF-8 decodes normally (ensure_ascii\u003dFalse on json.dumps).\n\n- Regenerate parser with ANTLR 4.13.2 to silence runtime-version-mismatch warning. \n  The generated lexer/parser were hardcoded to check for ANTLR runtime\n  4.11.1, which triggered a noisy \u0027ANTLR runtime and generated code\n  versions disagree\u0027 warning when installed against a newer runtime like\n  4.13.2. Regenerating from Agtype.g4 with the 4.13.2 tool aligns the\n  generated checkVersion() call with the default-installed runtime in\n  the allowed dependency range and eliminates the warning.\n\n- Bumps the declared floor of antlr4-python3-runtime to 4.13.2 so the\n  default install path is warning-free.\n\nMade-with: Cursor"
    },
    {
      "commit": "908d7b29811a23d7b01c024af5d6697fe670e123",
      "tree": "62c73ff47abcec18fb33ba6a249610778c870594",
      "parents": [
        "6c4083862fbf59309a7735aafa7ab1def1bda13d"
      ],
      "author": {
        "name": "Sai Asish Y",
        "email": "say.apm35@gmail.com",
        "time": "Mon Apr 20 02:32:54 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 20 14:32:54 2026 +0500"
      },
      "message": "Return an empty list from tail() for empty and singleton lists (#2399)\n\nage_tail() in src/backend/utils/adt/agtype.c had an explicit early return\nthat mapped both the empty-list and the singleton-list cases to SQL NULL:\n\n    count \u003d AGT_ROOT_COUNT(agt_arg);\n    /* if we have an empty list or only one element in the list, return null */\n    if (count \u003c\u003d 1)\n    {\n        PG_RETURN_NULL();\n    }\n\nThe Cypher specification (and Neo4j / Memgraph) both\ndefine tail(list) as \"the list without its first element\". For a\nzero-or-one-element input that is an empty list [], not null. Differential\ntests against Neo4j surface this divergence immediately.\n\nRemove the early return. The loop below already iterates from i\u003d1 so it\nnaturally emits [] for count \u003c\u003d 1 and emits the correct tail for the\ngeneral case. No behaviour change for count \u003e\u003d 2. Tests and expected\noutput are updated to match the new, spec-correct result."
    },
    {
      "commit": "6c4083862fbf59309a7735aafa7ab1def1bda13d",
      "tree": "09348b7d3a34571e485dede17dae40cd46728bc5",
      "parents": [
        "bdc8b6ddabe210ec02a77d232b742ced9c0d438e"
      ],
      "author": {
        "name": "Sai Asish Y",
        "email": "say.apm35@gmail.com",
        "time": "Mon Apr 20 02:19:01 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 20 14:19:01 2026 +0500"
      },
      "message": "Fix substring() crash when start-offset is NULL and length is supplied (#2401)\n\nage_substring() reads the null map produced by extract_variadic_args()\nand rejects null offset/length with this guard:\n\n    if ((nargs \u003d\u003d 2 \u0026\u0026 nulls[1]) ||\n        (nargs \u003d\u003d 3 \u0026\u0026 nulls[2]))\n    {\n        ereport(ERROR, ..., errmsg(\"substring() offset or length cannot be null\"));\n    }\n\nThe condition only checks nulls[1] in the 2-argument form. When the\ncaller passes `substring(str, null, len)` the function takes nargs \u003d 3,\nnulls[1] \u003d true, but the guard above does not fire. Execution reaches\nthe numeric-parameter loop below, which reads args[1] through\nDatumGetInt32 / DATUM_GET_AGTYPE_P without ever re-checking nulls[i].\nThe Datum in that slot is undefined, the dereference segfaults, and\nthe PostgreSQL backend terminates - not a query error but a\nconnection-level crash (#2386).\n\nWiden the guard to nargs \u003e\u003d 2 \u0026\u0026 nulls[1] so it catches start-is-null\nin both the 2-arg and 3-arg forms. nulls[2] is still only checked\nwhen nargs \u003d\u003d 3. No behaviour change on any non-null path; the\nconnection-crash case is now reported as a normal query error,\nmatching the intent the existing error message already implies."
    },
    {
      "commit": "bdc8b6ddabe210ec02a77d232b742ced9c0d438e",
      "tree": "a427e4d5b526618f1aeca478d42d4cbc7ef0a969",
      "parents": [
        "4f9db377cc5576403e891ba67f2c7d103aaebcd7"
      ],
      "author": {
        "name": "Ueslei Lima",
        "email": "uesleisantoslima@gmail.com",
        "time": "Mon Apr 20 06:16:33 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 20 14:16:33 2026 +0500"
      },
      "message": "fix(python-driver): quote-escape column aliases in buildCypher() (#2373)\n\n* fix(python-driver): quote-escape column aliases in buildCypher()\n\nAlways double-quote column names in the AS (...) clause generated by\nbuildCypher() and _validate_column(). This prevents PostgreSQL parse\nerrors when column aliases happen to be reserved words (e.g. \u0027count\u0027,\n\u0027order\u0027, \u0027type\u0027, \u0027group\u0027, \u0027select\u0027).\n\nBefore: SELECT * from cypher(NULL,NULL) as (count agtype);\nAfter:  SELECT * from cypher(NULL,NULL) as (\"count\" agtype);\n\nPostgreSQL always accepts double-quoted identifiers, so quoting\nunconditionally is safe for all names — no reserved word list needed.\n\nCloses #2370\n\n* Address review feedback: document type quoting, improve tests\n\n- _validate_column: add comment explaining why type_name is\n  intentionally left unquoted (PG type names are case-insensitive)\n- buildCypher: fix graphName \u003d\u003d None to idiomatic \u0027is None\u0027\n- test_reserved_word_count: replace fragile assertNotIn with regex\n- Add test for reserved word in \"name type\" pair (e.g. \"order agtype\")\n- Add comment explaining intentional _validate_column private import\n\n* Fix test_security.py for quoted column names, improve comment\n\n- Update test_security.py assertions to expect double-quoted column\n  names (consistent with the always-quote-column-names change)\n- Reword type_name comment to clarify case-folding semantics\n\nMade-with: Cursor"
    },
    {
      "commit": "4f9db377cc5576403e891ba67f2c7d103aaebcd7",
      "tree": "c5103d4abb9e80556dec90931521bb3a359d1927",
      "parents": [
        "15030a038278bf5ad0355ce2b80ccacc27bb3dc2"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Sun Apr 19 09:26:47 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Apr 19 18:26:47 2026 +0500"
      },
      "message": "Implement predicate functions: all(), any(), none(), single() (#2359)\n\n* Implement predicate functions: all(), any(), none(), single()\n\nImplement the four openCypher predicate functions (issues #552, #553,\n#555, #556) that test list elements against a predicate:\n\n  all(x IN list WHERE predicate)    -- true if all elements match\n  any(x IN list WHERE predicate)    -- true if at least one matches\n  none(x IN list WHERE predicate)   -- true if no elements match\n  single(x IN list WHERE predicate) -- true if exactly one matches\n\nImplementation approach:\n- Add cypher_predicate_function node type with CPFK_ALL/ANY/NONE/SINGLE\n  kind enum, reusing the list comprehension\u0027s unnest-based transformation\n- Grammar rules in expr_func_subexpr (alongside EXISTS, COALESCE, COUNT)\n- Transform to efficient SQL sublinks:\n  all()   -\u003e NOT EXISTS (SELECT 1 FROM unnest WHERE NOT pred)\n  any()   -\u003e EXISTS (SELECT 1 FROM unnest WHERE pred)\n  none()  -\u003e NOT EXISTS (SELECT 1 FROM unnest WHERE pred)\n  single() -\u003e (SELECT count(*) FROM unnest WHERE pred) \u003d 1\n- Three new keywords (ANY_P, NONE, SINGLE) added to safe_keywords for\n  backward compatibility as property keys and label names\n- Shared extract_iter_variable_name() helper for variable validation\n\nAll 32 regression tests pass. New predicate_functions test covers basic\nsemantics, empty lists, graph data integration, boolean combinations,\nnested predicates, and keyword backward compatibility.\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Address Copilot review: NULL semantics, iterator validation, single() perf, tests\n\n- Rewrite predicate functions from EXISTS_SUBLINK to EXPR_SUBLINK with\n  aggregate-based CASE expressions (bool_or + IS TRUE/FALSE/NULL) to\n  preserve three-valued Cypher NULL semantics\n- Add list_length check in extract_iter_variable_name() to reject\n  qualified names like x.y as iterator variables\n- Add copy/read support for cypher_predicate_function ExtensibleNode\n  to prevent query rewriter crashes\n- Use IS TRUE filtering in single() count (LIMIT 2 optimization\n  breaks correlated variable refs in graph contexts -- documented)\n- Add 13 NULL regression tests: null list input, null elements,\n  null predicates for all four functions\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Address Copilot round 2: NULL-list guard, single() comment, pg_aggregate.h\n\n1. Add NULL-list guard for all predicate functions (all/any/none/single).\n   Wraps the result with CASE WHEN list IS NULL THEN NULL ELSE \u003cresult\u003e\n   END in the grammar layer.  This fixes single(x IN null WHERE ...)\n   returning false instead of NULL.  The expr pointer is safely shared\n   between the NullTest and the predicate function node because AGE\u0027s\n   expression transformer creates new nodes without modifying the\n   parse tree in-place.\n\n2. Fix single() block comment in transform_cypher_predicate_function:\n   described LIMIT 2 optimization but implementation uses plain\n   count(*).  Updated comment to match actual implementation.\n\n3. Keep #include \"catalog/pg_aggregate.h\" -- Copilot suggested removal\n   but AGGKIND_NORMAL macro requires it (build fails without it).\n\nRegression test: predicate_functions OK.\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Address Copilot round 3: reuse extract_iter_variable_name for list comprehensions\n\n- Refactor build_list_comprehension_node() to reuse the shared\n  extract_iter_variable_name() helper, so `var IN list` validation\n  is consistent between list comprehensions and predicate functions\n  (all/any/none/single). Qualified ColumnRefs like `x.y IN list`\n  are now rejected in list comprehensions the same way they are\n  in predicate functions.\n- Update list_comprehension expected output for the normalized\n  lowercase \"syntax error at or near IN\" message.\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "15030a038278bf5ad0355ce2b80ccacc27bb3dc2",
      "tree": "2c69ae6822b7b673900d0b7f6da3d9c68072d432",
      "parents": [
        "ce5004593ea5acb2cec3a2dc94a06241d7f23c17"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Sun Apr 19 08:48:25 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Apr 19 17:48:25 2026 +0500"
      },
      "message": "Fix OPTIONAL MATCH dropping null-preserving rows with subquery WHERE (#2380)\n\n* Fix OPTIONAL MATCH dropping null-preserving rows with subquery WHERE\n\nCypher OPTIONAL MATCH semantics require that when no right-hand row\nsurvives the WHERE predicate, the outer row is still emitted with\nNULLs in the optional columns.  Before this fix, a WHERE containing\na list comprehension or sub-pattern predicate (EXISTS { ... },\nCOUNT { ... }) would take the transform_cypher_clause_with_where\nrewrite path, which detaches the WHERE, transforms the match clause\nas a subquery, and then attaches the WHERE as an outer filter on that\nsubquery.  For OPTIONAL MATCH, the inner subquery already produced a\nLATERAL LEFT JOIN with null-preserving rows; the outer filter then\nran against those nulled rows and dropped them when the predicate\nevaluated NULL or false on the nulled side, producing zero rows where\nCypher semantics require one null-filled row per outer match.\n\nFix: in transform_cypher_match, the has_list_comp_or_subquery rewrite\nnow only applies to non-optional MATCH.  In the OPTIONAL MATCH path,\ntransform_cypher_optional_match_clause detaches the WHERE from the\ncypher_match node before recursively transforming the right-hand side\n(so the inner transform does not double-apply or misresolve the\npredicate in a fresh namespace), and re-attaches the transformed\npredicate as the LEFT JOIN\u0027s ON condition after both sides are in the\nnamespace.  A LEFT JOIN with a failing ON condition correctly\npreserves left rows with null right columns, which matches Cypher\nOPTIONAL MATCH ... WHERE semantics.\n\nRegression tests cover:\n  - EXISTS { (friend)-[...]-\u003e(...) } referencing the optional variable\n  - EXISTS { (p)-[...]-\u003e(...) } referencing the outer variable\n  - non-correlated EXISTS (previously-working guard)\n  - plain scalar predicate on the optional variable (guard)\n  - constant-false WHERE (guard)\n\nFixes issue #2378.\n\nCo-Authored-By: Claude Opus 4.6 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "ce5004593ea5acb2cec3a2dc94a06241d7f23c17",
      "tree": "6dfb045045ddb1a44bfd16d917777aee7bd64cd8",
      "parents": [
        "f1a9b1d9b8b171656efd82674dec2e6ce06b8f9a"
      ],
      "author": {
        "name": "Ueslei Lima",
        "email": "uesleisantoslima@gmail.com",
        "time": "Sun Apr 19 09:10:39 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Apr 19 17:10:39 2026 +0500"
      },
      "message": "Python driver: Add `skip_load` parameter to skip `LOAD \u0027age\u0027` statement (#2366)\n\n* Add skip_load parameter to connect and setUpAge functions to control plugin loading.\n\n* Add tests and README docs for skip_load parameter\n\n- Add 4 unit tests for setUpAge() skip_load behavior:\n  skip_load\u003dTrue skips LOAD, skip_load\u003dFalse executes LOAD,\n  load_from_plugins integration, and search_path always set.\n- Document skip_load in README under new \"Managed PostgreSQL Usage\"\n  section for Azure/AWS RDS/etc. environments.\n- Fix syntax in existing load_from_plugins code example.\n\nMade-with: Cursor\n\n* Address review feedback: ValueError for contradictory flags, e2e test\n\n- Raise ValueError when skip_load\u003dTrue and load_from_plugins\u003dTrue are\n  both set (contradictory combination)\n- Add end-to-end test verifying skip_load is forwarded through the full\n  age.connect() → Age.connect() → setUpAge() call chain\n- Replace fragile string assertions with assert_called_with/assert_any_call\n- README: mention configure_connection() as the pool-based alternative\n  for managed PostgreSQL environments\n\nMade-with: Cursor\n\n* Fix README: use setUpAge(skip_load\u003dTrue) for pool example\n\nconfigure_connection() is not part of this PR; use the available\nsetUpAge() API with skip_load\u003dTrue for the connection pool example.\n\nMade-with: Cursor\n\n* fix(python-driver): use quoted $user in search_path and run TestSetUpAge in CI\n\nPostgreSQL treats single-quoted \u0027$user\u0027 as a literal schema name; use\n\"$user\" so the session user schema is included. Include TestSetUpAge in\nthe test_age_py __main__ suite so skip_load tests run in GitHub Actions.\n\nMade-with: Cursor"
    },
    {
      "commit": "f1a9b1d9b8b171656efd82674dec2e6ce06b8f9a",
      "tree": "a58e3367f867f9e74270e2487c2e20a9a2f35078",
      "parents": [
        "1847644cf7d2f52486f3a6dba90317ad72f178c7"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Thu Apr 16 11:15:34 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Apr 16 23:15:34 2026 +0500"
      },
      "message": "Fix upgrade test: build default install SQL from HEAD, not initial commit (#2397)\n\nThe upgrade test previously built age--\u003cCURR\u003e.sql from the initial\nversion-bump commit, meaning CREATE EXTENSION installed \u0027day-one\u0027\nSQL. This caused all 31 non-upgrade regression tests to run WITHOUT\nSQL functions added after the version bump (e.g., age_invalidate_graph_cache,\nage_prepare_pg_upgrade, age_vertex_stats, etc.). These functions were\nnever registered in pg_proc, so features depending on them (like VLE\ncache invalidation triggers) were silently disabled during testing.\n\nFix by inverting the upgrade test direction:\n\nBefore (installs incomplete SQL, upgrades to complete):\n  age--1.7.0.sql                     built from version-bump commit (incomplete)\n  age--1.7.0_upgrade_test.sql        built from HEAD (complete)\n  age--1.7.0--1.7.0_upgrade_test.sql stamped from template\n  Test: CREATE EXTENSION age -\u003e data -\u003e ALTER EXTENSION UPDATE TO \u00271.7.0_upgrade_test\u0027\n\nAfter (installs complete SQL, upgrades from synthetic initial):\n  age--1.7.0.sql                     built from HEAD (complete)\n  age--1.7.0_initial.sql             built from version-bump commit (synthetic)\n  age--1.7.0_initial--1.7.0.sql      stamped from template\n  Test: CREATE EXTENSION age VERSION \u00271.7.0_initial\u0027 -\u003e data -\u003e ALTER EXTENSION UPDATE TO \u00271.7.0\u0027\n\nThis ensures:\n  - All 31 non-upgrade tests run with every SQL function registered\n  - The upgrade template is still validated (initial -\u003e current)\n  - The test ends at the default version (clean state for drop test)\n  - Tarball builds (no git) work identically (cat sql/sql_files)\n  - All synthetic files are cleaned up after the test\n\nMakefile changes:\n  - age_sql rule: cat current HEAD sql/sql_files (was: git show from commit)\n  - New age_init_sql rule: git show from version-bump commit\n  - age_upgrade_test_sql: stamps template as INIT-\u003eCURR (was CURR-\u003eNEXT)\n  - EXTRA_CLEAN, _install_upgrade_test_files: updated filenames\n  - Removed AGE_NEXT_VER, age_next_sql; added AGE_INIT_VER, age_init_sql\n\nTest SQL changes:\n  - Step 3: CREATE EXTENSION age VERSION \u0027\u003cinit\u003e\u0027 (was: CREATE EXTENSION age)\n  - Step 6: ALTER EXTENSION age UPDATE TO default_version (was: LIKE \u0027%_upgrade_test\u0027)\n  - Step 7: Check installed \u003d default (was: installed \u003c\u003e default)\n  - Comments updated throughout to reflect inverted direction\n\nAll 32 regression tests pass.\n\nmodified:   Makefile\nmodified:   regress/expected/age_upgrade.out\nmodified:   regress/sql/age_upgrade.sql"
    },
    {
      "commit": "1847644cf7d2f52486f3a6dba90317ad72f178c7",
      "tree": "ba09c71b2ebdd330c4d794ced03d5f45f2eafc78",
      "parents": [
        "945a259d86d42204f186f79dec758585afde0eaa"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Wed Apr 15 01:09:50 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Apr 15 13:09:50 2026 +0500"
      },
      "message": "Improve extension upgrade regression test (addendum to #2364) (#2377)\n\nNote: This PR was created with AI tools and a human.\n\nThis is an addendum to PR #2364 with three improvements.\n\nMakefile:\n- Replace awk-based synthetic version (minor+1) with an _upgrade_test\n  suffix (e.g., 1.7.0 -\u003e 1.7.0_upgrade_test). The awk approach produced\n  numeric versions like 1.8.0 that could collide with real future upgrade\n  scripts, and the ::int[] cast in the SQL version lookup fails on\n  non-numeric version strings. The _upgrade_test suffix avoids both\n  issues and is unambiguously synthetic.\n- Extend the generated cleanup script to also remove repo-root copies\n  of the synthetic files and to self-delete, preventing stale artifacts\n  from accumulating across repeated test runs.\n\nRegression test (regress/sql/age_upgrade.sql):\n- Simplify version lookup to directly select the _upgrade_test version\n  via LIKE \u0027%_upgrade_test\u0027 instead of picking the highest non-default\n  version with string_to_array(version, \u0027.\u0027)::int[] DESC. The old\n  approach would fail with a cast error on the _upgrade_test suffix and\n  was unnecessarily indirect — the test knows exactly what synthetic\n  version the Makefile installed.\n\nmodified:   Makefile\nmodified:   regress/expected/age_upgrade.out\nmodified:   regress/sql/age_upgrade.sql\n\nCo-authored-by: Claude \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "945a259d86d42204f186f79dec758585afde0eaa",
      "tree": "3f67a6f4fb810672142c4339529140c4b2511cb5",
      "parents": [
        "b3a00eea32c3faa93c43e0d91c0a75671e48ce65"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Tue Apr 14 12:01:26 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Apr 14 21:01:26 2026 +0200"
      },
      "message": "Add missing include for PR: Add index scan (#2351) (#2379)\n\nThe CI build did not fail the PR (we need to verify why and correct\nit) due to the missing include -\n\nsrc/backend/utils/adt/age_global_graph.c:273:25: error: implicit declaration of\nfunction ‘namestrcpy’; did you mean ‘strcpy’? [-Wimplicit-function-declaration]\n  273 |                         namestrcpy(lval, NameStr(*label_name_ptr));\n      |                         ^~~~~~~~~~\n      |                         strcpy\n\nThe build still works as the linker was able to resolve it. This PR\nwill add it in to correct the error going forward."
    },
    {
      "commit": "b3a00eea32c3faa93c43e0d91c0a75671e48ce65",
      "tree": "f751d94a00c7d91fdf7a323aac510b6f40abe564",
      "parents": [
        "d6f1b7f67087bd86f2e0fe6e1b6c897b7e569926"
      ],
      "author": {
        "name": "a_bondar",
        "email": "s6311704@gmail.com",
        "time": "Wed Apr 15 01:23:53 2026 +0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Apr 14 11:23:53 2026 -0700"
      },
      "message": "Add index scan (#2351)\n\n* Add index scan\n\nThis commit fixes performance degradation during insertion scenarios by replacing SeqScan with IndexScan.\n\nMotivation / Problem:\nAs a result of load testing, a significant performance degradation was found in insertion scenarios. The scenarios\nused were taken from an open-source benchmark and rewritten in pure SQL. Examples of the queries can be \nfound here:\n\nhttps://github.com/ldbc/ldbc_snb_interactive_v1_impls/blob/main/cypher/queries/interactive-update-1.cypher\nhttps://github.com/ldbc/ldbc_snb_interactive_v1_impls/blob/main/cypher/queries/interactive-update-6.cypher\nhttps://github.com/ldbc/ldbc_snb_interactive_v1_impls/blob/main/cypher/queries/interactive-update-7.cypher\n\nPerf analysis showed that the main bottleneck is the entity_exists function. The root cause lies in the use of a\nSequential Scan (SeqScan) to check for the existence of an entity prior to insertion. The time complexity of a\nSeqScan is O(N), meaning the search time grows linearly as the number of rows in the table increases. The\nlarger the graph became, the longer each individual insertion took. This led to a drop in TPS regardless of\nthe concurrency level (the issue was consistently reproduced with both 1 and 30 threads).\n\nCo-authored-by: Daria Barsukova \u003cd.barsukova@g.nsu.ru\u003e\nCo-authored-by: Alexandra Bondar \u003cs6311704@gmail.com\u003e\n\n* Address code review commets\n\n* Address code review commets\n\n* Add brackets\n\n---------\n\nCo-authored-by: Alexandra Bondar \u003ca.bondar@postgrespro.ru\u003e\nCo-authored-by: Daria Barsukova \u003cd.barsukova@g.nsu.ru\u003e"
    },
    {
      "commit": "d6f1b7f67087bd86f2e0fe6e1b6c897b7e569926",
      "tree": "7c8e723de4c82fd28634dc50cf06591c0c88189a",
      "parents": [
        "a29e2810b1ac5a9e4c3e553879b683a6a1902c8b"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Mon Apr 06 11:34:47 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 06 08:34:47 2026 -0700"
      },
      "message": "Fix MATCH on brand-new label after CREATE returning 0 rows (#2341)\n\n* Fix MATCH on brand-new label after CREATE returning 0 rows (issue #2193)\n\nWhen CREATE introduces a new label and a subsequent MATCH references it\n(e.g., CREATE (:Person) WITH ... MATCH (p:Person)), the query returns\n0 rows on first execution but works on the second.\n\nRoot cause: match_check_valid_label() in transform_cypher_match() runs\nbefore transform_prev_cypher_clause() processes the predecessor chain.\nSince CREATE has not yet executed its transform (which creates the label\ntable as a side effect), the label is not in the cache and the check\ngenerates a One-Time Filter: false plan that returns no rows.\n\nFix: Skip the early label validity check when the predecessor clause\nchain contains a data-modifying operation (CREATE, SET, DELETE, MERGE).\nAfter transform_prev_cypher_clause() completes and any new labels exist\nin the cache, run a deferred label check. If the labels are still\ninvalid at that point, generate an empty result via makeBoolConst(false).\n\nThis preserves the existing behavior for MATCH without DML predecessors\n(e.g., MATCH-MATCH chains still get the early check and proper error\nmessages for invalid labels).\n\nDepends on: PR #2340 (clause_chain_has_dml helper)\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n* Address review feedback: fix variable registration for deferred label check\n\nWhen the deferred label validity check (DML predecessor + non-existent\nlabel) found an invalid label, the code skipped transform_match_pattern()\nentirely, which meant MATCH-introduced variables were never registered\nin the namespace. This would cause errors if a later clause referenced\nthose variables (e.g., RETURN p).\n\nFix: mirror the early-check strategy by injecting a paradoxical WHERE\n(true \u003d false) and always calling transform_match_pattern(). Variables\nget registered normally; zero rows are returned via the impossible qual.\n\nAlso add ORDER BY to multi-row regression tests for deterministic output,\nand add a test case for DML predecessor + non-existent label + returning\na MATCH-introduced variable.\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n* Address Copilot review: DRY false-where helper, cache has_dml, ORDER BY in tests\n\n- Factor duplicated WHERE true\u003dfalse construction into\n  make_false_where_clause() helper (used in both early and deferred\n  label validation paths)\n- Compute clause_chain_has_dml() once and reuse, avoiding repeated\n  clause chain traversal\n- Add ORDER BY to the single-CREATE City regression test for\n  deterministic result ordering\n\n* Address Copilot review: volatile false predicate, DML side-effect test\n\n1. Prevent plan elimination of DML predecessor: replace constant\n   (true \u003d false) with volatile (random() IS NULL) in the deferred\n   label check path. PG\u0027s planner can constant-fold the former into\n   a One-Time Filter: false, skipping the DML scan entirely.\n\n2. Unify make_false_where_clause(bool volatile_needed): merge the\n   constant and volatile variants into a single parameterized\n   function. Call sites are now self-documenting:\n   - make_false_where_clause(false) for non-DML path\n   - make_false_where_clause(true) for DML predecessor path\n\n3. Document why add_volatile_wrapper() cannot be reused here (it\n   operates post-transform at the Expr level and returns agtype,\n   while the WHERE clause is built at the parse-tree level).\n\n4. Add regression test verifying CREATE side effects persist when\n   MATCH references a non-existent label after a DML predecessor.\n\nAll regression tests pass (cypher_match: ok).\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n* Replace non-ASCII em dashes with -- in C comments\n\nASCII-only codebase convention; avoids encoding/tooling issues.\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "a29e2810b1ac5a9e4c3e553879b683a6a1902c8b",
      "tree": "cc54935eaba84efe8c9f3b28ef5291828b7e0531",
      "parents": [
        "90c33eb6b7c36f8280893bdc2b64666debb836de"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Mon Apr 06 03:28:46 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 06 15:28:46 2026 +0500"
      },
      "message": "Fix nondeterministic age_global_graph regression test (#2365)\n\nThe age_global_graph test had two issues that could cause intermittent\nfailures:\n\n1. Nondeterministic warning output: The graph_stats() call on a graph\n   with deliberately deleted vertices produces WARNING messages for\n   dangling edges. These warnings are emitted by iterating edge label\n   tables (knows, stalks), and the iteration order is not guaranteed.\n   Since PostgreSQL WARNING messages cannot be caught or counted from\n   SQL (only ERROR and above are catchable via PL/pgSQL exception\n   handling), we suppress them with SET client_min_messages \u003d error.\n   The suppressed warnings are documented verbatim in comments. The\n   graph_stats() result row still validates correct dangling-edge\n   handling.\n\n2. Nondeterministic row ordering: Multiple MATCH...RETURN queries\n   returned multi-row results without ORDER BY, relying on scan order.\n   Added ORDER BY id(u), id(v), id(e), id(n), or id(a) as appropriate\n   to all MATCH...RETURN queries for future-proofing, even those\n   currently returning a single row.\n\nFiles changed:\n  regress/sql/age_global_graph.sql\n  regress/expected/age_global_graph.out\n\nCo-authored-by: GitHub Copilot \u003cnoreply@github.com\u003e"
    },
    {
      "commit": "90c33eb6b7c36f8280893bdc2b64666debb836de",
      "tree": "bae2b661e266b63d326b87e37d425589eb460310",
      "parents": [
        "23146a44c36c0ece003b7b91f7942385ae8ac5e7"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Mon Apr 06 03:27:03 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Apr 06 15:27:03 2026 +0500"
      },
      "message": "Add extension upgrade template regression test (#2364)\n\nNote: This PR was created with AI tools and a human.\n\nAdd a version-agnostic regression test (age_upgrade) that validates the\nupgrade template (age--\u003cVER\u003e--y.y.y.sql) works correctly by simulating a\nfull extension version upgrade within \"make installcheck\".\n\nAdd full upgrade scripts to the install path (DATA) in the Makefile,\nexcluding template upgrade files. This enables the install to copy all\nversion upgrade files into the PG AGE install. This is needed for\nALTER EXTENSION\n\nAdjusted installcheck.yaml to allow git commit history for this test.\n\nMakefile infrastructure:\n- Build the install SQL (age--\u003cCURR\u003e.sql) from the initial version-bump\n  commit in git history, so CREATE EXTENSION installs \"day-one\" SQL while\n  the .so comes from current HEAD — implicitly testing backward compat.\n- Build a synthetic \"next\" version (age--\u003cNEXT\u003e.sql) from HEAD and stamp\n  the upgrade template to produce age--\u003cCURR\u003e--\u003cNEXT\u003e.sql.\n- Add an installcheck prerequisite that temporarily installs both synthetic\n  files into the PG extension directory; a generated cleanup script removes\n  them at the end of the test via \\! shell escape. EXTRA_CLEAN catches\n  stragglers on \"make clean\".\n- Skip the test automatically when: (a) no git history (tarball builds),\n  (b) no upgrade template exists, or (c) a real upgrade script from the\n  current version is already committed (detected via git ls-files).o\n\nRegression test (regress/sql/age_upgrade.sql):\n- Creates 3 graphs (company, network, routes) with 8 vertex labels,\n  8 edge labels, 23 vertices, 28 edges, and 4 GIN indexes.\n- Records integrity checksums (agtype sums), vertex/edge counts, and\n  label counts before the upgrade; repeats all checks after ALTER\n  EXTENSION UPDATE to the synthetic next version.\n- Verifies structural queries: VLE management chains, circular follow\n  chains, flight distances with edge properties.\n- Verifies all 4 GIN indexes survive the upgrade via pg_indexes.\n- Uses ORDER BY on all multi-row queries for deterministic output.\n- Returns agtype natively (no ::numeric casts) for portability.\n- Avoids version-dependent output (checks boolean IS NOT NULL instead\n  of printing the version string).\n- Uses JOIN-based label counts to avoid NULL comparison bugs with the\n  internal _ag_catalog graph.\n- Cleans up all 3 graphs and restores the default AGE version.\n\nmodified:   Makefile\nnew file:   regress/expected/age_upgrade.out\nnew file:   regress/sql/age_upgrade.sql\nmodified:   .github/workflows/installcheck.yaml"
    },
    {
      "commit": "23146a44c36c0ece003b7b91f7942385ae8ac5e7",
      "tree": "534bdd7ace54c6ac8cf6de56281647cc8a255c84",
      "parents": [
        "d0741d8dc90f0c2f636f049b48b9d41eb54516cf"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Tue Mar 03 12:52:48 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Mar 03 09:52:48 2026 -0800"
      },
      "message": "Fix crash in PREPARE with property parameter when enable_containment is off (#2339)\n\n* Fix crash in PREPARE with property parameter when enable_containment is off\n\nWhen age.enable_containment is set to off, executing a PREPARE statement\nwith a property parameter (e.g., MATCH (n $props) RETURN n) causes a\nsegfault. The crash occurs in transform_map_to_ind_recursive because\nthe property_constraints node is a cypher_param, not a cypher_map, but\nis blindly cast to cypher_map and its keyvals field is dereferenced.\n\nThree fixes:\n- In create_property_constraints, when enable_containment is off and the\n  constraint is a cypher_param, fall back to the containment operator\n  (@\u003e) since map decomposition requires known keys at parse time.\n- In transform_match_entities, guard the keep_null assignment for both\n  vertex and edge property constraints with is_ag_node checks to avoid\n  writing to the wrong struct layout.\n\nFixes #1964\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n* Fix @\u003e vs @\u003e\u003e for \u003dproperties form with PREPARE and add tests\n\nWhen MATCH uses the \u003dproperties form (e.g., MATCH (n \u003d $props)), the\nenable_containment\u003don path correctly uses @\u003e\u003e (top-level containment).\nThe parameter fallback path unconditionally used @\u003e (deep containment),\nignoring the use_equals flag. Fix the fallback to mirror the\nenable_containment path by selecting @\u003e\u003e when use_equals is set.\n\nAdd regression tests for \u003dproperties form with PREPARE for both\nvertices and edges, with enable_containment on and off.\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "d0741d8dc90f0c2f636f049b48b9d41eb54516cf",
      "tree": "3937e51275d5ac0b7dd06340fde90d4be1985d32",
      "parents": [
        "a21120bf139b156661ef10374630e688c0cc46fb"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Mon Mar 02 13:09:19 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Mar 02 10:09:19 2026 -0800"
      },
      "message": "Fix entity_exists() CID visibility for CREATE + WITH + MERGE (#2343)\n\n* Fix entity_exists() CID visibility for CREATE + WITH + MERGE (#1954)\n\nWhen a Cypher query chains CREATE ... WITH ... MERGE, vertices created\nby CREATE become invisible to entity_exists() after a threshold number\nof input rows. This causes MERGE to throw \"vertex assigned to variable\nwas deleted\".\n\nRoot cause: CREATE calls CommandCounterIncrement() which advances the\nglobal command ID, but does not update es_snapshot-\u003ecurcid. The\nDecrement/Increment CID macros used by the executors bring curcid back\nto the same value on each iteration. After enough rows, newly inserted\nvertices have a Cmin \u003e\u003d curcid and HeapTupleSatisfiesMVCC rejects them\n(requires Cmin \u003c curcid).\n\nFix: In entity_exists(), temporarily set es_snapshot-\u003ecurcid to the\ncurrent global command ID (via GetCurrentCommandId) for the duration\nof the scan, then restore it. This makes all entities inserted by\npreceding clauses in the same query visible to the existence check.\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n* Use Max() to prevent curcid regression in entity_exists()\n\nAddress review feedback: es_snapshot-\u003ecurcid can be ahead of the\nglobal CID due to Increment_Estate_CommandId macros. Unconditionally\nassigning GetCurrentCommandId(false) could decrease curcid, making\npreviously visible tuples invisible. Use Max(saved_curcid,\nGetCurrentCommandId(false)) to ensure we only ever increase visibility.\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "a21120bf139b156661ef10374630e688c0cc46fb",
      "tree": "e67db7efa4ca9728956810009b1859bac22c5024",
      "parents": [
        "217467a36a1c29df4b918faf6adb6e75aec28817"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Mon Mar 02 11:27:04 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Mar 02 08:27:04 2026 -0800"
      },
      "message": "Support doubled-quote escaping in Cypher string literals (issue #2222) (#2342)\n\nSQL drivers (psycopg2, JDBC, etc.) escape single quotes by doubling\nthem (\u0027isn\u0027\u0027t\u0027) when substituting parameters into queries. When these\nsubstitutions land inside Cypher\u0027s $$ block, the Cypher scanner rejects\nthem because it only recognizes backslash escaping (\\\u0027). This makes it\ndifficult to pass strings containing apostrophes through SQL drivers.\n\nAdd \u0027\u0027 and \"\" as escape sequences in the Cypher scanner, following the\nsame pattern already used for backtick-quoted identifiers (``). Flex\npicks the longer two-character match over the one-character closing\nquote, so the change is backwards-compatible -- \u0027\u0027 was previously a\nsyntax error.\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "217467a36a1c29df4b918faf6adb6e75aec28817",
      "tree": "c136a7bc5162ef8e4d7feb1eedc1fbcc6b0d038b",
      "parents": [
        "20ada845280c4370909d82b8652f6a082e5aa5df"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Fri Feb 27 17:10:04 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Feb 27 14:10:04 2026 -0800"
      },
      "message": "Fix MATCH after CREATE returning 0 rows (issue #2308) (#2340)\n\nWhen a MATCH clause follows CREATE + WITH and re-uses bound variables\n(e.g. CREATE (a)-[e]-\u003e(b) WITH a,e,b MATCH p\u003d(a)-[e]-\u003e(b)), the MATCH\ngenerates filter quals (age_start_id(e) \u003d age_id(a), etc.) that\nreference only columns from the predecessor subquery. PostgreSQL\u0027s\noptimizer pushes these quals through the transparent subquery layers\ninto the CREATE\u0027s child plan, where they evaluate on NULL values before\nCREATE has executed — always yielding 0 rows.\n\nFix: mark the predecessor subquery RTE as security_barrier when the\nclause chain contains a data-modifying operation (CREATE, SET, DELETE,\nor MERGE). This prevents PostgreSQL from pushing filter quals into the\nsubquery, ensuring they evaluate after the DML produces output values.\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "20ada845280c4370909d82b8652f6a082e5aa5df",
      "tree": "6945374c4ebd9925cc920f966b9bbb2c31856c5e",
      "parents": [
        "346f319459e18db89deae30a3af7ea945bba101d"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Fri Feb 27 15:05:31 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Feb 27 12:05:31 2026 -0800"
      },
      "message": "Fix chained MERGE not seeing sibling MERGE\u0027s changes (#1446) (#2344)\n\n* Fix chained MERGE not seeing sibling MERGE\u0027s changes (#1446)\n\nWhen multiple MERGEs are chained (e.g. MATCH ... MERGE ... MERGE ...),\nthe non-terminal (first) MERGE returned rows one at a time to the parent\nplan node. The parent MERGE\u0027s lateral join would materialize its hash\ntable on the first row, before the child MERGE had finished all its\niterations. This caused the second MERGE to not see entities created by\nthe first MERGE, leading to duplicate nodes.\n\nFix by making non-terminal MERGE eager: it processes ALL input rows and\nbuffers the projected results before returning any to the parent. This\nensures all entity creations are committed before any parent plan node\nscans the tables.\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n* Fix non-terminal MERGE empty-buffer fallthrough and add test\n\nWhen a non-terminal MERGE receives no input rows from its predecessor\n(e.g., MATCH returns 0 rows), the eager buffer is filled but empty.\nThe condition at line 688 checked `css-\u003eeager_tuples !\u003d NIL`, which\nevaluated to false for an empty buffer, causing execution to fall\nthrough to the terminal MERGE code path. This could incorrectly\ncreate entities when none should be created.\n\nFix by checking `css-\u003eeager_buffer_filled` instead, which correctly\ndistinguishes \"buffer not yet filled\" from \"buffer filled but empty\".\n\nAdd regression test for chained MERGE with empty MATCH result.\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "346f319459e18db89deae30a3af7ea945bba101d",
      "tree": "3a770416ae7880ad270ca70fcfb3227cb8d884c8",
      "parents": [
        "5005c21e5c2aa5daaca909fee7c4f9ed8ccdf984"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Thu Feb 26 19:24:58 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Feb 26 16:24:58 2026 -0800"
      },
      "message": "Fix VLE queries failing on read-only replicas (#2160) (#2345)\n\nThe global graph cache used by VLE acquired ShareLock when scanning\nvertex and edge label tables to populate in-memory hashtables. On\nread-only replicas (standby servers in recovery), PostgreSQL only\nallows RowExclusiveLock or less, so VLE queries would fail with:\n\n  \"cannot acquire lock mode ShareLock on database objects while\n   recovery is in progress\"\n\nChange all three functions in age_global_graph.c to use AccessShareLock\ninstead, which is sufficient for read-only table scans and is consistent\nwith the existing ag_cache.c code that performs identical operations.\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "5005c21e5c2aa5daaca909fee7c4f9ed8ccdf984",
      "tree": "dcbea7954f9fed7d88c44f41bd412bcb26330d0f",
      "parents": [
        "55476ad2875f37f5b65919b6eeab7d81fd65aa7c"
      ],
      "author": {
        "name": "Greg Felice",
        "email": "gregfelice@gmail.com",
        "time": "Thu Feb 26 18:30:56 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Feb 26 15:30:56 2026 -0800"
      },
      "message": "Fix VLE NULL handling for chained OPTIONAL MATCH (#2337)\n\n* Fix VLE NULL handling for chained OPTIONAL MATCH (#2092)\n\nVLE functions (age_match_vle_terminal_edge, age_match_two_vle_edges,\nage_match_vle_edge_to_id_qual) threw errors when receiving NULL\narguments from OPTIONAL MATCH (LEFT JOIN) contexts. Additionally,\nbuild_local_vle_context crashed with a segfault when dereferencing\na NULL next_vertex pointer in the cached VLE context path.\n\nThese functions are used as join quals. In a LEFT JOIN, NULL arguments\nmean the inner side produced no match. The correct response is FALSE\n(no match), which lets PostgreSQL emit NULL-extended rows — the\nexpected OPTIONAL MATCH behavior. Errors or crashes are incorrect.\n\nChanges:\n- build_local_vle_context: guard against NULL next_vertex in cached\n  path; return NULL when vertex list is exhausted\n- age_vle: handle NULL return from build_local_vle_context with\n  SRF_RETURN_DONE\n- age_match_vle_terminal_edge: return FALSE on NULL arguments instead\n  of ereport(ERROR)\n- age_match_two_vle_edges: return FALSE on NULL arguments\n- age_match_vle_edge_to_id_qual: return FALSE on NULL arguments\n\nAll 32 regression tests pass including new tests for this fix.\n\n* Address review feedback: fix error message and add ORDER BY to tests\n\n- Fix errmsg in age_match_vle_terminal_edge() to use the correct\n  function name (was age_match_terminal_edge)\n- Add ORDER BY p.name to regression test queries to avoid\n  nondeterministic row ordering in expected output\n\nAI-assisted: Claude (Anthropic) was used in developing this fix.\n\nCo-Authored-By: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 4.6 \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "55476ad2875f37f5b65919b6eeab7d81fd65aa7c",
      "tree": "cc7a6430d0df83947374b029ecc969b55c8c95bb",
      "parents": [
        "3c4d9cc7456703f37d3f2d02e4cc79731b06542a"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Wed Feb 25 09:41:41 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Feb 25 22:41:41 2026 +0500"
      },
      "message": "fix incorrect variable assignment (#2336)\n\nFixed an incorrect variable assignment, that was causing a warning\nmessage during compilation, on some compilers.\n\nThe create_index_on_column function assigned InvalidOid, instead\nof NIL or NULL.\n\n-    index_col-\u003ecollation \u003d InvalidOid;\n+    index_col-\u003ecollation \u003d NIL;\n\nNo regression tests were impacted.\n\nmodified:   src/backend/commands/label_commands.c"
    },
    {
      "commit": "3c4d9cc7456703f37d3f2d02e4cc79731b06542a",
      "tree": "6f8f5540d908d910a1e6a16136bd8f516804c904",
      "parents": [
        "fa91350b0a524e596e4fc85c9086565c60c00548"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Mon Feb 16 22:09:01 2026 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Feb 16 09:09:01 2026 -0800"
      },
      "message": "Remove labeler github action (#2335)\n\n- dropped the labeler workflow because `pull_request_target` trigger\n  can be dangerous and the workflow wasn’t particularly useful."
    },
    {
      "commit": "fa91350b0a524e596e4fc85c9086565c60c00548",
      "tree": "2c4b38b3d7673207035d4ff3376e169b22bac314",
      "parents": [
        "0c9a527860cde710175fc9c171b6bbe9aaed45d6"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Feb 13 23:24:54 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Feb 14 12:24:54 2026 +0500"
      },
      "message": "Fix ISO C90 forbids mixed declarations and code warning (#2334)\n\nFixed the following ISO C90 warning -\n\nsrc/backend/utils/adt/agtype.c:7503:9: warning: ISO C90 forbids mixed declarations\nand code [-Wdeclaration-after-statement]\n 7503 |         enum agtype_value_type elem_type \u003d elem ? elem-\u003etype : AGTV_NULL;\n      |         ^~~~\n\nNo regression tests impacted.\n\nmodified:   src/backend/utils/adt/agtype.c"
    },
    {
      "commit": "0c9a527860cde710175fc9c171b6bbe9aaed45d6",
      "tree": "3330d15d98f2634e44690b45488be243b36e852e",
      "parents": [
        "77a16ece0bbd8f137ad40b4e203c9622c249352d"
      ],
      "author": {
        "name": "Maxim Korotkov",
        "email": "korotkov.maxim.s@gmail.com",
        "time": "Fri Feb 13 21:57:51 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Feb 13 10:57:51 2026 -0800"
      },
      "message": "Fix null pointer handling in array iteration (#2313)\n\nPreviously, when iterating through an agtype container, the code would\naccess `elem-\u003eval` even when `elem` was null.\nThis adds a null check to set the result type to AGTV_NULL when the\nelement is null, preventing a potential segmentation fault.\n\nFixes: 4274f10 (\"Added the toStringList() function (#1084)\")\nFound by PostgresPro.\n\nSigned-off-by: Maksim Korotkov \u003cm.korotkov@postgrespro.ru\u003e"
    },
    {
      "commit": "77a16ece0bbd8f137ad40b4e203c9622c249352d",
      "tree": "91d8c3563b726194daf25dd2570cc709502b8f77",
      "parents": [
        "5f5b744a08641225652de83332d73bc7acfc889d"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Feb 13 09:31:35 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Feb 13 22:31:35 2026 +0500"
      },
      "message": "Fix security vulnerabilities in Node.js driver (#2329)\n\nFix security vulnerabilities in Node.js driver and harden input\nvalidation and query construction.\n\nNote: This PR was created with AI tools and a human.\n\n- Add input validation for graph names, label names, and column names\n  to prevent SQL injection via string interpolation. Graph name rules\n  are based on Apache AGE\u0027s naming conventions and Neo4j/openCypher\n  compatibility (hyphens and dots permitted, min 3 chars, max 63 chars).\n  Label names follow AGE\u0027s stricter rules (no hyphens or dots).\n- Add design documentation noting intentional ASCII-only restriction\n  in driver-side regex validation as a security hardening measure\n  (homoglyph/encoding attack surface reduction). AGE\u0027s server-side\n  validation (name_validation.h) uses full Unicode ID_Start/ID_Continue\n  and remains the authoritative check for Unicode names.\n- Add safe query helpers: queryCypher(), createGraph(), dropGraph()\n  with graph name validation and dollar-quoting for Cypher strings\n- Add runtime typeof check on dropGraph cascade parameter to prevent\n  injection from plain JavaScript consumers (TypeScript types are\n  erased at runtime)\n- Use BigInt for integer values exceeding Number.MAX_SAFE_INTEGER to\n  prevent silent precision loss with 64-bit AGE graph IDs\n- Make CREATE EXTENSION opt-in via SetAGETypesOptions.createExtension\n  instead of running DDL automatically without user consent\n- Wrap LOAD/search_path setup in try/catch with actionable error\n  message mentioning CREATE EXTENSION and { createExtension: true }\n- Improve agtype-not-found error message with installation guidance\n- Tighten pg dependency from \u003e\u003d6.0.0 to \u003e\u003d8.0.0\n- Add comprehensive test suites covering validation, SQL injection\n  prevention, cascade type safety, hyphenated graph name integration,\n  BigInt parsing, and setAGETypes error handling\n- Add design note in tests documenting why createExtension: false is\n  the correct default (CI image has AGE pre-installed, auto-creating\n  extensions requires SUPERUSER and conflates concerns)\n\nmodified:   drivers/nodejs/package.json\nmodified:   drivers/nodejs/src/antlr4/CustomAgTypeListener.ts\nmodified:   drivers/nodejs/src/index.ts\nmodified:   drivers/nodejs/test/Agtype.test.ts\nmodified:   drivers/nodejs/test/index.test.ts"
    },
    {
      "commit": "5f5b744a08641225652de83332d73bc7acfc889d",
      "tree": "3db8c8e0ef0889e79d8f01ac22f02e251d150d61",
      "parents": [
        "887564d9ce0f153ad7dc9f30223aa303c3fb60d0"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Feb 13 09:30:05 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Feb 13 22:30:05 2026 +0500"
      },
      "message": "Update python-driver security and formatting (#2330)\n\nNote: This PR was created with AI tools and a human.\n\n- Add parameterized query construction using psycopg.sql to prevent\n  SQL injection in all Cypher execution paths (age.py, networkx/lib.py)\n- Replace all %-format and f-string SQL in networkx/lib.py with\n  sql.Identifier() for schema/table names and sql.Literal() for values\n- Add validate_graph_name() with AGE-aligned VALID_GRAPH_NAME regex:\n  start with letter/underscore, allow dots and hyphens in middle positions,\n  end with letter/digit/underscore, min 3 chars, max 63 chars\n- Add validate_identifier() with strict VALID_IDENTIFIER regex for labels,\n  column names, and SQL types (no dots or hyphens)\n- Add validation calls to all networkx/lib.py entry points:\n  graph names validated on entry, labels validated before SQL construction\n- Add _validate_column() to sanitize column specifications in buildCypher()\n- Fix exception constructors (AgeNotSet, GraphNotFound, GraphAlreadyExists)\n  to always call super().__init__() with a meaningful default message so\n  that str(exception) never returns an empty string\n- Add InvalidGraphName and InvalidIdentifier exception classes with\n  structured name/reason/context fields\n- Fix builder.py: change erroneous \u0027return Exception(...)\u0027 to\n  \u0027raise ValueError(...)\u0027 for unknown float expressions\n- Fix copy-paste docstring in create_elabel() (\u0027create_vlabels\u0027 -\u003e \u0027create_elabels\u0027)\n- Remove unused \u0027from psycopg.adapt import Loader\u0027 import in age.py\n- Add design documentation in source explaining:\n  - VALID_GRAPH_NAME regex uses \u0027*\u0027 (not \u0027+\u0027) intentionally so that the\n    min-length check fires first with a clear error message\n  - buildCypher uses string concatenation (not sql.Identifier) because\n    column specs are pre-validated \u0027name type\u0027 pairs that don\u0027t map to\n    sql.Identifier(); graphName and cypherStmt are NOT embedded\n- Update test_networkx.py GraphNotFound assertion to use assertIn()\n  instead of assertEqual() to match the improved exception messages\n- Strip Windows carriage returns (^M) from 7 source files\n- Fix requirements.txt: convert from UTF-16LE+BOM+CRLF to clean UTF-8+LF,\n  move --no-binary flag from requirements.txt to CI workflow pip command\n- Upgrade actions/setup-python from v4 (deprecated) to v5 in CI workflow\n- Add 46 security unit tests in test_security.py covering:\n  - Graph name validation (AGE naming rules, injection, edge cases)\n  - SQL identifier validation (labels, columns, types)\n  - Column spec sanitization\n  - buildCypher injection prevention\n  - Exception constructor correctness (str() never empty)\n- Add test_security.py to CI pipeline (python-driver.yaml)\n- pip-audit: 0 known vulnerabilities in all dependencies\n\nmodified:   .github/workflows/python-driver.yaml\nmodified:   drivers/python/age/VERSION.py\nmodified:   drivers/python/age/__init__.py\nmodified:   drivers/python/age/age.py\nmodified:   drivers/python/age/builder.py\nmodified:   drivers/python/age/exceptions.py\nmodified:   drivers/python/age/models.py\nmodified:   drivers/python/age/networkx/lib.py\nmodified:   drivers/python/requirements.txt\nmodified:   drivers/python/setup.py\nmodified:   drivers/python/test_agtypes.py\nmodified:   drivers/python/test_networkx.py\nnew file:   drivers/python/test_security.py"
    },
    {
      "commit": "887564d9ce0f153ad7dc9f30223aa303c3fb60d0",
      "tree": "46c8503f088337ba2cffaa9cb190093194aaff6b",
      "parents": [
        "5fe2121a06f692efc9a8c3d77b57c67b874e1b69"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Feb 13 01:54:06 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Feb 13 14:54:06 2026 +0500"
      },
      "message": "Fix JDBC driver CI test failures (#2333)\n\nFix JDBC driver CI test failures: encoding, Testcontainers, and\nDocker compatibility.\n\nNote: This PR was created with the help of AI tools and a human.\n\n- Set JavaCompile encoding to UTF-8 to fix unicode test failures in\n  CI environments that default to US-ASCII.\n\n- Add Testcontainers wait strategy (Wait.forLogMessage) to wait for\n  PostgreSQL to be fully ready before connecting.\n\n- Use agensGraphContainer.getHost() instead of hardcoded localhost\n  for Docker-in-Docker compatibility.\n\n- Add sslmode\u003ddisable to JDBC URL since PostgreSQL driver 42.6.0+\n  attempts SSL by default.\n\n- Remove silent exception swallowing around connection setup to fail\n  fast with meaningful errors instead of NullPointerException.\n\n- Upgrade Testcontainers from 1.18.0 to 1.21.4 to fix Docker 29.x\n  detection failure on ubuntu-24.04 runners (docker-java 3.3.x in\n  1.18.0 cannot negotiate with Docker Engine 29.1.5 API).\n\nmodified:   drivers/jdbc/lib/build.gradle.kts\nmodified:   drivers/jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java"
    },
    {
      "commit": "5fe2121a06f692efc9a8c3d77b57c67b874e1b69",
      "tree": "53fa0a0da6fd59635306507e95f5ccdb62adf7f4",
      "parents": [
        "858747c7e2a414651b4f8e76558fdce1bbdc0af8"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Tue Feb 10 10:32:29 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Feb 10 23:32:29 2026 +0500"
      },
      "message": "Add pg_upgrade support functions for PostgreSQL (#2326)\n\nAdd pg_upgrade support functions for PostgreSQL for major version\nupgrades\n\nNOTE: This PR was created with AI tools and a human.\n\nThe ag_graph.namespace column uses the regnamespace type, which pg_upgrade\ncannot handle in user tables. This commit adds four SQL functions to enable\nseamless PostgreSQL major version upgrades while preserving all graph data.\n\nNew functions in ag_catalog:\n- age_prepare_pg_upgrade(): Converts namespace from regnamespace to oid,\n  creates backup table with graph-to-namespace mappings (stores nspname\n  directly to avoid quoting issues)\n- age_finish_pg_upgrade(): Remaps stale OIDs after upgrade, restores\n  regnamespace type, invalidates AGE caches while preserving schema ownership\n- age_revert_pg_upgrade_changes(): Cancels preparation if upgrade is aborted\n- age_pg_upgrade_status(): Returns current upgrade readiness status\n\nUsage:\n  1. Before pg_upgrade: SELECT age_prepare_pg_upgrade();\n  2. Run pg_upgrade as normal\n  3. After pg_upgrade:  SELECT age_finish_pg_upgrade();\n\nKey implementation details:\n- Uses transaction-level advisory locks (pg_advisory_xact_lock) for safety\n- Preserves original schema ownership during cache invalidation\n- Validates all backup rows are mapped before proceeding\n- Handles zero-graph edge case gracefully\n- Handles insufficient privileges gracefully with informative notices\n- Backup table deleted only after all steps succeed\n\nFiles changed:\n- sql/age_pg_upgrade.sql: New file with function implementations\n- sql/sql_files: Added age_pg_upgrade entry\n- age--1.7.0--y.y.y.sql: Added functions for extension upgrades\n\nAll regression tests pass."
    },
    {
      "commit": "858747c7e2a414651b4f8e76558fdce1bbdc0af8",
      "tree": "052e8a143a5a4187910077ed16fa1b3c8ed32a00",
      "parents": [
        "6287af85f291e10d048c37ffd22b902950779af7"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Thu Jan 22 09:58:14 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jan 22 22:58:14 2026 +0500"
      },
      "message": "Advance master branch to Apache AGE version 1.7.0 (#2316)\n\nUpdated the following files to advance the Apache AGE version\nto 1.7.0\n\nmodified:   Makefile\nmodified:   README.md\nmodified:   RELEASE\nrenamed:    age--1.6.0--y.y.y.sql -\u003e age--1.6.0--1.7.0.sql\nnew file:   age--1.7.0--y.y.y.sql\nmodified:   age.control\nmodified:   docker/Dockerfile\ndeleted:    age--1.5.0--1.6.0.sql"
    },
    {
      "commit": "6287af85f291e10d048c37ffd22b902950779af7",
      "tree": "0ec19095337cada5e92ec849f6ab30a26fbd0585",
      "parents": [
        "b3219fd4228cc261df06070e37512c76b8c07678"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Thu Jan 22 21:50:23 2026 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jan 22 08:50:23 2026 -0800"
      },
      "message": "Fix upgrade script for 1.6.0 to 1.7.0 (#2320)\n\n- Added index creation for existing labels\n\nAssisted-by AI"
    },
    {
      "commit": "b3219fd4228cc261df06070e37512c76b8c07678",
      "tree": "762be11d32cc6df8c097b057ed08e50914624178",
      "parents": [
        "1702ae075de6897e9555fa527060f622304a1d93"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Wed Jan 21 10:01:57 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jan 21 23:01:57 2026 +0500"
      },
      "message": "Master to PostgreSQL version 18 (#2315)\n\n* Updated CI, Labeler, Docker, and branch security files for PG18 (#2246)\n\nUpdated the CI and Docker files for the PG18\nUpdated the labeler and branch security files for PG18.\n\nSome of these only apply to the master branch but are updated\nfor consistency.\n\nmodified:   .asf.yaml\nmodified:   .github/labeler.yml\nmodified:   .github/workflows/go-driver.yml\nmodified:   .github/workflows/installcheck.yaml\nmodified:   .github/workflows/jdbc-driver.yaml\nmodified:   .github/workflows/nodejs-driver.yaml\nmodified:   .github/workflows/python-driver.yaml\nmodified:   docker/Dockerfile\nmodified:   docker/Dockerfile.dev\nmodified:   drivers/docker-compose.yml\n\n* PG18 port for AGE (#2251)\n\n* [PG18 port][Set1] Fix header dependencies and use TupleDescAttr macro\n\n- Include executor/executor.h for PG18 header reorganization and use \n  TupleDescAttr() accessor macro instead of direct attrs[] access.\n\n* [PG18 port][Set2] Adapt to expandRTE signature change and pg_noreturn\n\n- Add VarReturningType parameter to expandRTE() calls using VAR_RETURNING_DEFAULT.\n- Replace pg_attribute_noreturn() with pg_noreturn prefix specifier.\n\n* [PG18 port][Set3] Fix double ExecOpenIndices call for PG18 compatibility\n\n- PG18 enforces stricter assertions in ExecOpenIndices, requiring\n  ri_IndexRelationDescs to be NULL when called.\n\n- In update_entity_tuple(), indices may already be opened by the\n  caller (create_entity_result_rel_info), causing assertion failures.\n\n- Add a check to only open indices if not already open, and track\n  ownership with a boolean flag to ensure we only close what we opened.\n\n- Found when regression tests failed with assertions, which this change\n  resolves.\n\n* [PG18 port][Set4] Update regression test expected output for ordering\n\nPG18\u0027s implementation changes result in different row ordering for\nqueries without explicit ORDER BY clauses. Update expected output\nfiles to reflect the new ordering while maintaining identical\nresult content.\n\n* [PG18 port][Set5] Address review comments - coding standard fix\n\nNote: Assisted by GitHub Copilot Agent mode.\n\n* Fix DockerHub build warning messages (#2252)\n\nPR fixes build warning messages on DockerHub and on my local build.\n\nNo regression tests needed.\n\nmodified:   src/include/nodes/ag_nodes.h\nmodified:   src/include/optimizer/cypher_createplan.h\nmodified:   src/include/optimizer/cypher_pathnode.h\nmodified:   tools/gen_keywordlist.pl\n\n---------\nCo-authored-by: Krishnakumar R (KK) \u003c65895020+kk-src@users.noreply.github.com\u003e"
    },
    {
      "commit": "1702ae075de6897e9555fa527060f622304a1d93",
      "tree": "071de4a0d3981a3f2bed9bd4239ccc73d8234e20",
      "parents": [
        "b29ca5e7d2f84cfe2619eea70c4ace2cba41aa0b"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Tue Jan 20 23:44:19 2026 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jan 20 10:44:19 2026 -0800"
      },
      "message": "Add RLS support and fix permission checks (#2309)\n\n- Previously, age only set ACL_SELECT and ACL_INSERT in RTEPermissionInfo,\n  bypassing pg\u0027s privilege checking for DELETE and UPDATE operations.\n- Additionally, RLS policies were not enforced because AGE uses CMD_SELECT\n  for all Cypher queries, causing the rewriter to skip RLS policy application.\n\nPermission fixes:\n- Add ACL_DELETE permission flag for DELETE clause operations\n- Add ACL_UPDATE permission flag for SET/REMOVE clause operations\n- Recursively search RTEs including subqueries for permission info\n\nRLS support:\n- Implemented at executor level because age transforms all cypher\n  queries to CMD_SELECT, so pg\u0027s rewriter never adds RLS\n  policies for INSERT/UPDATE/DELETE operations. There isnt an\n  appropriate rewriter hook to modify this behavior, so we do it\n  in executor instead.\n- Add setup_wcos() to apply WITH CHECK policies at execution time\n  for CREATE, SET, and MERGE operations\n- Add setup_security_quals() and check_security_quals() to apply\n  USING policies for UPDATE and DELETE operations\n- USING policies silently filter rows (matching pg behavior)\n- WITH CHECK policies raise errors on violation\n- DETACH DELETE raises error if edge RLS blocks deletion to prevent\n  dangling edges\n- Add permission checks and rls in startnode/endnode functions\n- Add regression tests\n\nAssisted-by AI"
    },
    {
      "commit": "b29ca5e7d2f84cfe2619eea70c4ace2cba41aa0b",
      "tree": "5587482b9187b9556706ae8f0a1de2b2fa415ab5",
      "parents": [
        "56a92d8c1be364e07bac51665a362ca91957194b"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Mon Jan 19 22:21:02 2026 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jan 19 09:21:02 2026 -0800"
      },
      "message": "Replace libcsv with pg COPY for csv loading (#2310)\n\n- Commit also adds permission checks\n- Resolves a critical memory spike issue on loading large file\n- Use pg\u0027s COPY infrastructure (BeginCopyFrom, NextCopyFromRawFields)\n  for 64KB buffered CSV parsing instead of libcsv\n- Add byte based flush threshold (64KB) matching COPY behavior for memory safety\n- Use heap_multi_insert with BulkInsertState for optimized batch inserts\n- Add per batch memory context to prevent memory growth during large loads\n- Remove libcsv dependency (libcsv.c, csv.h)\n- Improves loading performance by 15-20%\n- No previous regression tests were impacted\n- Added regression tests for permissions/rls\nAssisted-by AI"
    },
    {
      "commit": "56a92d8c1be364e07bac51665a362ca91957194b",
      "tree": "68ae7e3af1606efbf51dfa6d60f6a8b2f77466d2",
      "parents": [
        "8bdeec54e898451771f4dc021a13b00781d6d1a0"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sun Jan 18 10:02:55 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jan 18 23:02:55 2026 +0500"
      },
      "message": "Fix Issue 1884: Ambiguous column reference (#2306)\n\nFix Issue 1884: Ambiguous column reference and invalid AGT header\nerrors.\n\nNote: This PR was created with AI tools and a human, or 2.\n\nThis commit addresses two related bugs that occur when using SET to store\ngraph elements (vertices, edges, paths) as property values:\n\nIssue 1884 - \"column reference is ambiguous\" error:\nWhen a Cypher query uses the same variable in both the SET expression RHS\nand the RETURN clause (e.g., SET n.prop \u003d n RETURN n), PostgreSQL would\nreport \"column reference is ambiguous\" because the variable appeared in\nmultiple subqueries without proper qualification.\n\nSolution: The fix for this issue was already in place through the target\nentry naming scheme that qualifies column references.\n\n\"Invalid AGT header value\" offset error:\nWhen deserializing nested VERTEX, EDGE, or PATH values stored in properties,\nthe system would fail with errors like \"Invalid AGT header value: 0x00000041\".\nThis occurred because ag_serialize_extended_type() did not include alignment\npadding (padlen) in the agtentry length calculation for these types, while\nfill_agtype_value() uses INTALIGN() when reading, causing offset mismatch.\n\nSolution: Modified ag_serialize_extended_type() in agtype_ext.c to include\npadlen in the agtentry length for VERTEX, EDGE, and PATH cases, matching\nthe existing pattern used for INTEGER, FLOAT, and NUMERIC types:\n\n    *agtentry \u003d AGTENTRY_IS_AGTYPE | (padlen + (AGTENTRY_OFFLENMASK \u0026 ...));\n\nThis ensures the serialized length accounts for alignment padding, allowing\ncorrect deserialization of nested graph elements.\n\nAppropriate regression tests were added to verify the fixes.\n\nCo-authored by: Zainab Saad \u003c105385638+Zainab-Saad@users.noreply.github.com\u003e\n\nmodified:   regress/expected/cypher_set.out\nmodified:   regress/sql/cypher_set.sql\nmodified:   src/backend/parser/cypher_clause.c\nmodified:   src/backend/utils/adt/agtype_ext.c"
    },
    {
      "commit": "8bdeec54e898451771f4dc021a13b00781d6d1a0",
      "tree": "9b832ab09abb86f195c566a4aa14e885b4c87951",
      "parents": [
        "b9d0982892306abff0013dd8f336e153684b02e9"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sat Jan 17 03:18:10 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jan 17 16:18:10 2026 +0500"
      },
      "message": "Upgrade Jest to v29 for node: protocol compatibility (#2307)\n\nNote: This PR was created with AI tools and a human.\n\nThe pg-connection-string module (dependency of pg) now uses the node:\nprotocol prefix for built-in modules (e.g., require(\u0027node:process\u0027)).\nJest 26 does not support this syntax, causing test failures.\n\nChanges:\n- Upgrade jest from ^26.6.3 to ^29.7.0\n- Upgrade ts-jest from ^26.5.1 to ^29.4.6\n- Upgrade @types/jest from ^26.0.20 to ^29.5.14\n- Update typescript to ^4.9.5\n\nThis also resolves 19 npm audit vulnerabilities (17 moderate, 2 high)\nthat existed in the older Jest 26 dependency tree.\n\nmodified:   drivers/nodejs/package.json"
    },
    {
      "commit": "b9d0982892306abff0013dd8f336e153684b02e9",
      "tree": "bc7bf98751e942cd136bae6bd9aecbdba76798b5",
      "parents": [
        "c979380e9865a624ad73eef01ec84717bb817f2b"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Jan 16 15:12:22 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jan 17 04:12:22 2026 +0500"
      },
      "message": "Optimize vertex/edge field access with direct array indexing (#2302)\n\nNOTE: This PR was created using AI tools and a human.\n\nLeverage deterministic key ordering from uniqueify_agtype_object() to\naccess vertex/edge fields in O(1) instead of O(log n) binary search.\n\nFields are sorted by key length, giving fixed positions:\n- Vertex: id(0), label(1), properties(2)\n- Edge: id(0), label(1), end_id(2), start_id(3), properties(4)\n\nChanges:\n- Add field index constants and accessor macros to agtype.h\n- Update age_id(), age_start_id(), age_end_id(), age_label(),\n  age_properties() to use direct field access\n- Add fill_agtype_value_no_copy() for read-only scalar extraction\n  without memory allocation\n- Add compare_agtype_scalar_containers() fast path for scalar comparison\n- Update hash_agtype_value(), equals_agtype_scalar_value(), and\n  compare_agtype_scalar_values() to use direct field access macros\n- Add fast path in get_one_agtype_from_variadic_args() bypassing\n  extract_variadic_args() for single argument case\n- Add comprehensive regression test (30 tests)\n\nPerformance impact: Improves ORDER BY, hash joins, aggregations, and\nCypher functions (id, start_id, end_id, label, properties) on vertices\nand edges.\n\nAll previous regression tests were not impacted.\nAdditional regression test added to enhance coverage.\n\nmodified:   Makefile\nnew file:   regress/expected/direct_field_access.out\nnew file:   regress/sql/direct_field_access.sql\nmodified:   src/backend/utils/adt/agtype.c\nmodified:   src/backend/utils/adt/agtype_util.c\nmodified:   src/include/utils/agtype.h"
    },
    {
      "commit": "c979380e9865a624ad73eef01ec84717bb817f2b",
      "tree": "ce149789a3d319fbee5f39feef99288f8f316fc0",
      "parents": [
        "a1f472d6f9344dc4449ac7343bf7d81a31b66f02"
      ],
      "author": {
        "name": "Jean-Paul Abbuehl",
        "email": "jp.abbuehl@gmail.com",
        "time": "Mon Jan 12 21:10:08 2026 +0100"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jan 12 12:10:08 2026 -0800"
      },
      "message": "feat: Add 32-bit platform support for graphid type (#2286)\n\n* feat: Add 32-bit platform support for graphid type\n\nThis enables AGE to work on 32-bit platforms including WebAssembly (WASM).\n\nProblem:\n- graphid is int64 (8 bytes) with PASSEDBYVALUE\n- On 32-bit systems, Datum is only 4 bytes\n- PostgreSQL rejects pass-by-value types larger than Datum\n\nSolution:\n- Makefile-only change (no C code modifications)\n- When SIZEOF_DATUM\u003d4 is passed to make, strip PASSEDBYVALUE from the generated SQL\n- If not specified, normal 64-bit behavior is preserved (PASSEDBYVALUE kept)\n\nThis change is backward compatible:\n- 64-bit systems continue using pass-by-value\n- 32-bit systems now work with pass-by-reference\n\nMotivation: PGlite (PostgreSQL compiled to WebAssembly) uses 32-bit\npointers and requires this patch to run AGE.\n\nTested on:\n- 64-bit Linux (regression tests pass)\n- 32-bit WebAssembly via PGlite (all operations work)\n\nCo-authored-by: abbuehlj \u003cjean-paul.abbuehl@roche.com\u003e"
    },
    {
      "commit": "a1f472d6f9344dc4449ac7343bf7d81a31b66f02",
      "tree": "7a8ce4eae52f05d88a0bf4e60ab1923373039ca7",
      "parents": [
        "7beb653303529b05391667de4204ffb4da318eeb"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Sat Jan 10 08:37:54 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jan 10 21:37:54 2026 +0500"
      },
      "message": "Fix and improve index.sql addendum (#2301)\n\nNOTE: This PR was created with the help of AI tools and a human.\n\nAdded additional requested regression tests -\n\n *EXPLAIN for pattern with WHERE clause\n\n *EXPLAIN for pattern with filters on both country and city\n\nmodified:   regress/expected/index.out\nmodified:   regress/sql/index.sql"
    },
    {
      "commit": "7beb653303529b05391667de4204ffb4da318eeb",
      "tree": "b32ad7b1b69f4ff7dd40bcb3e085d265928963a0",
      "parents": [
        "2e8f7ab992fc5db6cedf88cbdffc15df3a3cf932"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Jan 09 12:55:36 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jan 10 01:55:36 2026 +0500"
      },
      "message": "Fix and improve index.sql regression test coverage (#2300)\n\nNOTE: This PR was created with AI tools and a human.\n\n- Remove unused copy command (leftover from deleted agload_test_graph test)\n- Replace broken Section 4 that referenced non-existent graph with\n  comprehensive WHERE clause tests covering string, int, bool, and float\n  properties with AND/OR/NOT operators\n- Add EXPLAIN tests to verify index usage:\n  - Section 3: Validate GIN indices (load_city_gin_idx, load_country_gin_idx)\n    show Bitmap Index Scan for property matching\n  - Section 4: Validate all expression indices (city_country_code_idx,\n    city_id_idx, city_west_coast_idx, country_life_exp_idx) show Index Scan\n    for WHERE clause filtering\n\nAll indices now have EXPLAIN verification confirming they are used as expected.\n\nmodified:   regress/expected/index.out\nmodified:   regress/sql/index.sql"
    },
    {
      "commit": "2e8f7ab992fc5db6cedf88cbdffc15df3a3cf932",
      "tree": "c2e9d3d1245cab4b7fce973f3b99f957bdc08ec4",
      "parents": [
        "4eeceab256f4795e6e8fb1d802935bdf403e5e93"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Fri Jan 09 12:27:47 2026 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jan 10 01:27:47 2026 +0500"
      },
      "message": "Fix Issue 2289: handle empty list in IN expression (#2294)\n\nNOTE: This PR was created with AI tools and a human.\n\nWhen evaluating \u0027x IN []\u0027 with an empty list, the transform_AEXPR_IN\nfunction would return NULL because no expressions were processed.\nThis caused a \u0027cache lookup failed for type 0\u0027 error downstream.\n\nThis fix adds an early check for the empty list case:\n- \u0027x IN []\u0027 returns false (nothing can be in an empty list)\n\nAdditional NOTE: Cypher does not have \u0027NOT IN\u0027 syntax. To check if\na value is NOT in a list, use \u0027NOT (x IN list)\u0027. The NOT operator\nwill invert the false from an empty list to true as expected.\n\nThe fix returns a boolean constant directly, avoiding the NULL result\nthat caused the type lookup failure.\n\nAdded regression tests.\n\nmodified:   regress/expected/expr.out\nmodified:   regress/sql/expr.sql\nmodified:   src/backend/parser/cypher_expr.c"
    },
    {
      "commit": "4eeceab256f4795e6e8fb1d802935bdf403e5e93",
      "tree": "c7836d4e9c790a2705e1ed2c2f13874998073bf4",
      "parents": [
        "dd6deb70e34e2ab75836dc6d037fde5a360d7075"
      ],
      "author": {
        "name": "M15terHyde",
        "email": "59905806+M15terHyde@users.noreply.github.com",
        "time": "Tue Jan 06 12:52:33 2026 -0600"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jan 06 10:52:33 2026 -0800"
      },
      "message": "Revise README for Python driver updates (#2298)\n\nUpdated README to from psycopg2 to psycopg3 (psycopg)"
    },
    {
      "commit": "dd6deb70e34e2ab75836dc6d037fde5a360d7075",
      "tree": "01d792af46e87ae59c472f070ad73db28a01b991",
      "parents": [
        "48fca83d90f29cf68fd9748db3b2d628f4978749"
      ],
      "author": {
        "name": "Arthur Nascimento",
        "email": "tureba@gmail.com",
        "time": "Tue Jan 06 13:36:49 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jan 06 08:36:49 2026 -0800"
      },
      "message": "Makefile: fix race condition on cypher_gram_def.h (#2273)\n\nThe file cypher_gram.c generates cypher_gram_def.h, which is directly\nnecessary for cypher_parser.o and cypher_keywords.o and their respective\n.bc files.\n\nBut that direct dependency is not reflected in the Makefile, which only\nhad the indirect dependency of .o on .c. So on high parallel builds, the\n.h may not have been generated by bison yet.\n\nAdditionally, the .bc files should have the same dependencies as the .o\nfiles, but those are lacking.\n\nHere is an example output where the .bc file fails to build, as it was\nrunning concurrently with the bison instance that was about to finalize\ncypher_gram_def.h:\n\n  In file included from src/backend/parser/cypher_parser.c:24:\n  clang-17 -Wno-ignored-attributes -fno-strict-aliasing -fwrapv -fexcess-precision\u003dstandard -Wno-unused-command-line-argument -Wno-compound-token-split-by-macro -O2  -I.//src/include -I.//src/include/parser -I. -I./ -I/usr/pgsql-17/include/server -I/usr/pgsql-17/include/internal -D_GNU_SOURCE  -I/usr/include -I/usr/include/libxml2  -flto\u003dthin -emit-llvm -c -o src/backend/parser/cypher_parser.bc src/backend/parser/cypher_parser.c\n  .//src/include/parser/cypher_gram.h:65:10: fatal error: \u0027parser/cypher_gram_def.h\u0027 file not found\n     65 | #include \"parser/cypher_gram_def.h\"\n        |          ^~~~~~~~~~~~~~~~~~~~~~~~~~\n  1 error generated.\n  make: *** [/usr/pgsql-17/lib/pgxs/src/makefiles/../../src/Makefile.global:1085: src/backend/parser/cypher_parser.bc] Error 1\n  make: *** Waiting for unfinished jobs....\n  gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror\u003dvla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough\u003d3 -Wshadow\u003dcompatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision\u003dstandard -Wno-format-truncation -O2 -g -fmessage-length\u003d0 -D_FORTIFY_SOURCE\u003d2 -fstack-protector -funwind-tables -fasynchronous-unwind-tables -fPIC -fvisibility\u003dhidden -I.//src/include -I.//src/include/parser -I. -I./ -I/usr/pgsql-17/include/server -I/usr/pgsql-17/include/internal -D_GNU_SOURCE  -I/usr/include -I/usr/include/libxml2   -c -o src/backend/catalog/ag_label.o src/backend/catalog/ag_label.c\n  /usr/bin/bison -Wno-deprecated  --defines\u003dsrc/include/parser/cypher_gram_def.h -o src/backend/parser/cypher_gram.c src/backend/parser/cypher_gram.y\n\nPreviously, cypher_parser.o was missing the dependency, so it could\nstart before cypher_gram_def.h was available:\n\n Considering target file \u0027src/backend/parser/cypher_parser.o\u0027.\n  File \u0027src/backend/parser/cypher_parser.o\u0027 does not exist.\n  Considering target file \u0027src/backend/parser/cypher_parser.c\u0027.\n  File \u0027src/backend/parser/cypher_parser.c\u0027 was considered already.\n  Considering target file \u0027src/backend/parser/cypher_gram.c\u0027.\n  File \u0027src/backend/parser/cypher_gram.c\u0027 was considered already.\n Finished prerequisites of target file \u0027src/backend/parser/cypher_parser.o\u0027.\n Must remake target \u0027src/backend/parser/cypher_parser.o\u0027.\n\nAs well as cypher_parser.bc, missing the dependency on\ncypher_gram_def.h:\n\n Considering target file \u0027src/backend/parser/cypher_parser.bc\u0027.\n  File \u0027src/backend/parser/cypher_parser.bc\u0027 does not exist.\n  Considering target file \u0027src/backend/parser/cypher_parser.c\u0027.\n  File \u0027src/backend/parser/cypher_parser.c\u0027 was considered already.\n Finished prerequisites of target file \u0027src/backend/parser/cypher_parser.bc\u0027.\n Must remake target \u0027src/backend/parser/cypher_parser.bc\u0027.\n\nNow cypher_parser.o correctly depends on cypher_gram_def.h:\n\n Considering target file \u0027src/backend/parser/cypher_parser.o\u0027.\n  File \u0027src/backend/parser/cypher_parser.o\u0027 does not exist.\n  Considering target file \u0027src/backend/parser/cypher_parser.c\u0027.\n  File \u0027src/backend/parser/cypher_parser.c\u0027 was considered already.\n  Considering target file \u0027src/backend/parser/cypher_gram.c\u0027.\n  File \u0027src/backend/parser/cypher_gram.c\u0027 was considered already.\n  Considering target file \u0027src/include/parser/cypher_gram_def.h\u0027.\n  File \u0027src/include/parser/cypher_gram_def.h\u0027 was considered already.\n Finished prerequisites of target file \u0027src/backend/parser/cypher_parser.o\u0027.\n Must remake target \u0027src/backend/parser/cypher_parser.o\u0027.\n\nAnd cypher_parser.bc correctly depends on cypher_gram_def.h as well:\n\n Considering target file \u0027src/backend/parser/cypher_parser.bc\u0027.\n  File \u0027src/backend/parser/cypher_parser.bc\u0027 does not exist.\n  Considering target file \u0027src/backend/parser/cypher_parser.c\u0027.\n  File \u0027src/backend/parser/cypher_parser.c\u0027 was considered already.\n  Considering target file \u0027src/backend/parser/cypher_gram.c\u0027.\n  File \u0027src/backend/parser/cypher_gram.c\u0027 was considered already.\n  Considering target file \u0027src/include/parser/cypher_gram_def.h\u0027.\n  File \u0027src/include/parser/cypher_gram_def.h\u0027 was considered already.\n Finished prerequisites of target file \u0027src/backend/parser/cypher_parser.bc\u0027.\n Must remake target \u0027src/backend/parser/cypher_parser.bc\u0027."
    },
    {
      "commit": "48fca83d90f29cf68fd9748db3b2d628f4978749",
      "tree": "12f2e80989b270d97c25f235a0f0b423b998bd69",
      "parents": [
        "838926cc35ae64d3514c656959b749719e904b09"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Tue Dec 16 08:33:28 2025 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Dec 16 21:33:28 2025 +0500"
      },
      "message": "Restrict age_load commands (#2274)\n\nThis PR applies restrictions to the following age_load commands -\n\n    load_labels_from_file()\n    load_edges_from_file()\n\nThey are now tied to a specific root directory and are required to have a\nspecific file extension to eliminate any attempts to force them to access\nany other files.\n\nNothing else has changed with the actual command formats or parameters,\nonly that they work out of the /tmp/age directory and only access files\nwith an extension of .csv.\n\nAdded regression tests and updated the location of the csv files for\nthose regression tests.\n\nmodified:   regress/expected/age_load.out\nmodified:   regress/sql/age_load.sql\nmodified:   src/backend/utils/load/age_load.c"
    },
    {
      "commit": "838926cc35ae64d3514c656959b749719e904b09",
      "tree": "d5f2d2be2f856dff7fa28b46cdcebf01ad48694c",
      "parents": [
        "1bb95bf616abe733dd6d5c02aa32c7f419827646"
      ],
      "author": {
        "name": "Muhammad Taha Naveed",
        "email": "mtaha@apache.org",
        "time": "Thu Dec 11 21:51:12 2025 +0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Dec 11 08:51:12 2025 -0800"
      },
      "message": "Migrate python driver configuration to pyproject.toml (#2272)\n\n- Add pyproject.toml with package configuration\n- Simplify setup.py to minimal backward-compatible wrapper.\n- Updated CI workflow and .gitignore.\n- Resolves warning about using setup.py directly."
    },
    {
      "commit": "1bb95bf616abe733dd6d5c02aa32c7f419827646",
      "tree": "a48f4367309c0a1d1d54b057ae2bc2326dc582da",
      "parents": [
        "91de779aee0ee241bf8022b4822a9745c8bc9fa3"
      ],
      "author": {
        "name": "jsell-rh",
        "email": "jsell@redhat.com",
        "time": "Thu Dec 11 10:56:37 2025 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Dec 11 20:56:37 2025 +0500"
      },
      "message": "Convert string to raw string to remove invalid escape sequence warning (#2267)\n\n- Changed \u0027\\s\u0027 to r\u0027\\s\u0027"
    },
    {
      "commit": "91de779aee0ee241bf8022b4822a9745c8bc9fa3",
      "tree": "f5af4ddc4d7ba716923362006450356fbdca9f1e",
      "parents": [
        "898481a8c79381cc9f49ca71a9756a6216b61353"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Thu Dec 11 07:32:13 2025 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Dec 11 20:32:13 2025 +0500"
      },
      "message": "Update grammar file for maintainability (#2270)\n\nConsolidated duplicate code, added helper functions, and reviewed\nthe grammar file for issues.\n\nNOTE: I used an AI tool to review and cleanup the grammar file. I\n      have reviewed all of the work it did.\n\nImprovements:\n\n1. Added KEYWORD_STRDUP macro to eliminate hardcoded string lengths\n2. Consolidated EXPLAIN statement handling into make_explain_stmt helper\n3. Extracted WITH clause validation into validate_return_item_aliases helper\n4. Created make_default_return_node helper for subquery return-less logic\n\nBenefits:\n\n- Reduced code duplication by ~150 lines\n- Improved maintainability with helper functions\n- Eliminated manual string length calculations (error-prone)\n\nAll 29 existing regression tests pass\n\nmodified:   src/backend/parser/cypher_gram.y"
    },
    {
      "commit": "898481a8c79381cc9f49ca71a9756a6216b61353",
      "tree": "c1a1fa1a9b854a45eeb04f58a403895328ab765d",
      "parents": [
        "0ea94644f25bdc08c803ff6521bfd412a6690401"
      ],
      "author": {
        "name": "John Gemignani",
        "email": "jrgemignani@gmail.com",
        "time": "Wed Dec 10 09:08:36 2025 -0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Dec 10 22:08:36 2025 +0500"
      },
      "message": "Fix ORDER BY alias resolution with AS in Cypher queries (#2269)\n\nNOTE: This PR was partially created with AI tools and reviewed by a human.\n\nORDER BY clauses failed when referencing column aliases from RETURN:\n\n    MATCH (p:Person) RETURN p.age AS age ORDER BY age DESC\n    ERROR: could not find rte for age\n\nAdded SQL-99 compliant alias matching to find_target_list_entry() that\nchecks if ORDER BY identifier matches a target list alias before\nattempting expression transformation. This enables standard SQL behavior\nfor sorting by aliased columns with DESC/DESCENDING/ASC/ASCENDING.\n\nUpdated regression tests.\n\nAdded regression tests.\nmodified:   regress/expected/cypher_match.out\nmodified:   regress/expected/expr.out\nmodified:   regress/sql/expr.sql\nmodified:   src/backend/parser/cypher_clause.c"
    }
  ],
  "next": "0ea94644f25bdc08c803ff6521bfd412a6690401"
}
