{#218-agensgraph-2180} # AgensGraph 2.18.6.0 2.18.6.0 릴리스는 AgensGraph를 PostgreSQL 18 위로 옮기고 ISO/IEC 39075(GQL) 표준의 상당 부분을 더합니다. 또한 타입 컬럼 속성 승격을 들여와, 자주 쓰이는 `jsonb` 속성이 쿼리를 고치지 않고도 실제로 인덱싱할 수 있는 컬럼이 되게 합니다. 잘 돌던 2.17 애플리케이션을 망가뜨릴 변경이 여럿 있습니다. 업그레이드하기 전에 아래 [2.18로 옮겨 가기](#218-migrating-to-218)를 읽고, [2.18로 업그레이드하기](upgrade-to-2.18.md)의 절차를 따르십시오. 2.17 데이터 디렉터리는 2.18 바이너리로 기동할 수 없습니다. {#218-overview} ## 개요 * **PostgreSQL 18 기반.** 2.17은 PostgreSQL 17.10 위에, 2.18.6.0은 18.6 위에 만들어졌으므로 PostgreSQL 18의 새로운 것이 모두 함께 옵니다. * **GQL.** 새 문장 `LET`·`NEXT`·`FILTER`·`FINISH`·`INSERT`·`FOR`, `CALL { }` 서브쿼리와 `CALL … YIELD`, `OPTIONAL CALL`, 서브쿼리 값 표현식 `EXISTS { }`·`COUNT { }`·`COLLECT { }`· `ARRAY { }`·`VALUE { }`·`x IN { }`, 독립 절이 된 `ORDER BY`·`SKIP`·`OFFSET`·`LIMIT`, `XOR` 연산자, 그리고 `CURRENT_GRAPH` / `CURRENT_PROPERTY_GRAPH`입니다. [GQL 적합성](../appendix/gql-conformance.md)을 참고하십시오. * **타입 컬럼 속성 승격.** `ALTER VLABEL movie ADD COLUMN year int GENERATED`는 속성에 선언한 타입의, 인덱싱할 수 있는 실제 컬럼을 주고, Cypher의 속성 읽기가 쿼리를 고치지 않고도 그것으로 해석됩니다. [속성을 컬럼으로 승격하기](../querying/typed-columns.md)를 참고하십시오. * **네이티브 배열에 담기는 그래프 요소.** 정점·간선·경로에 대한 `collect()`가 jsonb 사본이 아니라 `vertex[]`·`edge[]`·`graphpath[]`를 만들므로, 모은 요소를 그대로 쓸 수 있습니다. * **순회에 대한 플래너 작업.** `ag_graphmeta`에 기반한 스캔 쳐내기, 쓰이지 않는 패턴 끝점 제거, 가변 길이 간선의 추정 개선이 들어 있습니다. * **크래시 15종 수정.** 그중 몇은 읽기 권한만으로도 닿을 수 있었습니다. {#218-new-features} ## 새 기능 {#218-gql-statements} ### GQL 문장 | 문장 | 하는 일 | | --- | --- | | [`LET`](../reference/clauses/let.md) | 범위 안의 것을 하나도 버리지 않고 계산한 값을 행에 더합니다. | | [`NEXT`](../reference/clauses/next.md) | 한 쿼리를 다른 쿼리의 결과에 이어 붙입니다. | | [`FILTER`](../reference/clauses/filter.md) | 행을 걸러 냅니다. `FILTER`와 `FILTER WHERE` 형태가 있습니다. | | [`FINISH`](../reference/clauses/finish.md) | 아무것도 투영하지 않는 쿼리를 끝냅니다. | | [`INSERT`](../reference/clauses/insert.md) | 요소를 만듭니다. `CREATE`의 GQL 표기입니다. | | [`FOR`](../reference/clauses/for.md) | 리스트를 순회하며, 인덱스는 `WITH OFFSET`으로 받습니다. | | [`CALL { }`](../reference/clauses/call-subquery.md) | 서브쿼리이며, 가져올 변수를 명시적으로 적습니다. | | [`CALL … YIELD`](../reference/clauses/call-yield.md) | 함수를 호출하고 그 출력 컬럼의 이름을 정합니다. | | [`OPTIONAL CALL`](../reference/clauses/optional-call.md) | `CALL`과 같지만, 아무것도 내놓지 않는 호출도 행 하나를 만듭니다. | | [서브쿼리 표현식](../reference/clauses/subquery-expressions.md) | 서브쿼리에 대한 `EXISTS { }`와 `VALUE { }`입니다. `COUNT { }`·`COLLECT { }`·`ARRAY { }`·`x IN { }`도 같은 일을 하지만 GQL이 아니라 AgensGraph 확장입니다. | `ORDER BY`·`SKIP`·`OFFSET`·`LIMIT`이 `RETURN`과 `WITH`의 일부가 아니라 그 자체로 절이 되었으므로 파이프라인 어디에나 놓을 수 있습니다. 쿼리를 끝맺지는 못합니다. 문장은 여전히 `RETURN`·`FINISH`나 쓰기 절로 끝나야 합니다. 그 밖에 새로 생긴 것은 `XOR` 연산자, `NULLIF`, `IS [NOT] UNKNOWN`, `RETURN ALL` / `WITH ALL`, `exists(property)`, `size(value)`, `cardinality()`, `toInteger()`, `toFloat()`, 그리고 Cypher 안에서 쓰는 SQL 값 함수입니다. 이제 형 변환에 키워드 타입을 쓸 수 있습니다. `::int`·`::numeric`·`::varchar`·`::bit`· `::interval`·`::boolean`·`::date`·`::timestamptz` 등입니다. 그중 몇은 전에는 통하는 표기가 아예 없었습니다. `::timestamp`와 `::time`은 여전히 Cypher에서 쓸 수 없습니다. 그 둘은 표현식을 SQL로 감싸서 쓰십시오. {#218-typed-column-property-promotion} ### 타입 컬럼 속성 승격 속성은 요소마다의 `jsonb` 문서 안에 있으므로, 읽기는 JSON 문서를 훑게 되고 값 자신의 타입에 대한 인덱스를 쓸 수 없습니다. 승격은 속성 하나에 선언한 타입의, 생성되어 저장되는 컬럼을 줍니다: ```cypher ALTER VLABEL movie ADD COLUMN year int GENERATED; CREATE PROPERTY INDEX ON movie (year); ``` 쿼리는 바뀌지 않습니다. 이제 `MATCH (m:movie) WHERE m.year > 2010`은 `m.year`를 그 컬럼으로 해석하고 인덱스를 쓸 수 있습니다. 승격은 레이블을 만들 때도 되며, 컬럼이 이름이 다른 속성에서 값을 받을 수도 있습니다: ```cypher CREATE VLABEL doc (words int GENERATED (word_count), score numeric GENERATED); ``` 컬럼은 괄호 안에 적은 속성을 담으므로, `words`로 해석되는 읽기는 `d.word_count`입니다. 컬럼 이름이 아니라 속성 키입니다. 확장 타입도 됩니다. 검색에서 중요해지는 것이 바로 이 경우입니다. `pgvector`를 설치했다면 `CREATE VLABEL doc (emb vector(1024) GENERATED)`가 임베딩에, 읽을 때마다 형 변환하는 `jsonb` 배열이 아니라 인덱싱할 수 있는 실제 컬럼을 줍니다. 승격된 키는 더 이상 스키마에서 자유롭지 않습니다. 값이 컬럼의 타입에 맞지 않는 쓰기는 중단됩니다. 그 득실은 [속성을 컬럼으로 승격하기](../querying/typed-columns.md)가 다룹니다. {#218-other-additions} ### 그 밖에 더해진 것 * `ALTER VLABEL … ADD COLUMN` / `DROP COLUMN`입니다. * 어느 컬럼이 어느 속성을 담는지 기록하는 새 카탈로그 `ag_label_property`, 그리고 스캔 쳐내기의 관문이 되는 새 컬럼 `ag_graph.graphmeta_valid`입니다. [시스템 카탈로그](../system_catalogs/catalogs.md)를 참고하십시오. * 새 설정 세 개 — `enable_graph_ddl`·`enable_property_promotion`· `enable_graph_endpoint_elision`입니다. [서버 설정](../reference/guc.md)을 참고하십시오. * 이제 `log_statement = mod`가 Cypher 쓰기를 기록합니다. 전에는 데이터를 바꾸는 문장을 남기라고 한 서버가 그중 어느 것도 남기지 않았습니다. SQL `INSERT`는 기록되고 그 옆의 Cypher `CREATE`는 그러지 않았습니다. {#218-migrating-to-218} ## 2.18로 옮겨 가기 지금 잘 돌고 있는 것을 망가뜨릴 가능성이 가장 큰 변경들입니다. 전체 목록과 절차는 [2.18로 업그레이드하기](upgrade-to-2.18.md)에 있습니다. {#218-new-reserved-words} ### 새 예약어 이제 `xor`·`current_graph`·`current_property_graph`가 예약어입니다. 그 이름을 가진 식별자· 레이블·변수·별칭은 따옴표로 감싸야 하며, 여기서 따옴표란 SQL에서와 마찬가지로 Cypher에서도 **큰따옴표**입니다 — `MATCH (n:"xor")`. 백틱은 여기서 따옴표가 아니므로 `` `xor` `` 는 구문 오류입니다. `insert`·`finish`·`let`도 예약어이지만, 2.16과 2.17에서 통하던 쓰임 두 가지는 일부러 되살렸습니다. 변수로 쓰는 `insert`, 그리고 *패턴 안에서* 레이블로 쓰는 `insert`나 `finish`입니다. 그런 레이블을 만들 때는 여전히 따옴표가 필요합니다. `CREATE VLABEL insert;` 는 구문 오류이고 `CREATE VLABEL "insert";` 는 되며, 그러고 나면 따옴표 없는 패턴 표기가 그것을 찾습니다. 어느 자리에서든 여전히 따옴표가 필요한 것은 *변수* 이름으로 쓰는 `let`이나 `finish`, 레이블로 쓰는 `let`, 그리고 `let`이나 `finish`라는 별칭으로 정렬하는 것입니다. 어느 단어가 어느 자리에서 실패하는지의 전체 표는 [키워드](../appendix/keywords.md)에 있습니다. `CALL`은 예약어로 만들지 않았으므로 `call`은 컬럼·테이블·별칭·레이블·속성 키·변수로 계속 쓸 수 있습니다. 뜻이 달라져 따옴표가 필요한 표기는 정확히 둘입니다. `call(...)` — 이름이 글자 그대로 `call`인 함수를 인수와 함께 호출하는 것 — 과, 변수 이름이 `call`이고 레이블 없이 속성 맵만 지닌 정점 패턴 `(call { ... })`입니다. [키워드](../appendix/keywords.md)를 참고하십시오. {#218-a-condition-takes-only-a-boolean} ### 조건은 불리언만 받는다 `WHERE n.count`와 `FILTER n.name`은 속성을 읽어 0이 아닌지, 비어 있지 않은지로 행을 골랐습니다. 이제 형 변환 오류를 냅니다: ``` ERROR: cannot cast "Jack" (jsonb type string) to boolean ``` `WHERE`·`FILTER`·`AND`·`OR`·`NOT`·`XOR`·`CASE WHEN`과 `dijkstra()`의 한정자에 적용됩니다. 명시적인 `::boolean`은 여전히 변환하며, 진짜 불리언과 jsonb null, 없는 키는 영향을 받지 않습니다. 의도한 변경입니다. ISO/IEC 39075는 검색 조건을 불리언 값 표현식으로 정의합니다. {#218-changed-result-types} ### 달라진 결과 타입 * **정점·간선·경로에 대한 `collect()`** 는 사본을 담은 jsonb 배열이 아니라 `vertex[]`·`edge[]`· `graphpath[]`를 반환합니다. 모은 요소에서 속성을 읽는 것이 이제 됩니다. 전에는 조용히 빈 값을 답했습니다. *속성*에 대한 `collect`는 여전히 jsonb 리스트를 반환하므로 저장된 데이터의 모양은 그대로입니다. * **정점이나 간선에 대한 `min()`과 `max()`는 이제 오류입니다**: ``` ERROR: function min(vertex) is not unique ``` PostgreSQL 18이 `min(record)`와 `max(record)`를 더했고, 그것이 암시적 `vertex → jsonb` 형 변환을 거쳐 `min(jsonb)`/`max(jsonb)`와 경쟁하게 됐습니다. 명시적으로 변환하거나 — `min(v::jsonb)` — 식별자를 집계하십시오. `min(id(v))` 입니다. * **`range()`** 는 정수 배열이 아니라 jsonb 배열을 반환합니다. * **`toStringList`·`toIntegerList`·`toFloatList`** 는 다시 작성했습니다. 각각 오버로드가 둘입니다. SQL에서 실제 배열에 대해 부르면 `text[]`·`bigint[]`·`double precision[]`을 반환하고, `jsonb`인 Cypher 리스트에 대해 부르면 `jsonb`를 반환합니다. 예전 것들은 여러 가지를 조용히 틀리게 답했습니다. bigint가 상위 비트를 잃었고, 숫자가 아닌 문자열이 0으로 변환됐으며, 원소가 하나인 리스트가 빈 채로 돌아왔고, 64자를 넘는 원소가 고정 버퍼를 넘어갔습니다. {#218-changed-result-sets} ### 달라진 결과 집합 **`SKIP`이나 `LIMIT`이 붙은 `WITH … WHERE`는 이제 걸러 낸 뒤에 나눕니다.** 전에는 나누기가 먼저 일어나서 `WITH x WHERE x > 1 ORDER BY x LIMIT 2`가 행 하나를 반환할 수 있었습니다. 이제 순서는 `WHERE`, 그다음 `ORDER BY`, 그다음 `SKIP`/`LIMIT`입니다. 결과 집합이 달라집니다. **`RETURN AS d ORDER BY d`는 jsonb 형태가 아니라 값 자체를 정렬합니다.** 유한하지 않은 실수를 빼면 순서가 있는 모든 타입에서 행과 정렬은 그대로입니다. 그 실수는 전에 문자열 사이에 정렬됐습니다. 얻는 것은 인덱스가 그 정렬을 맡을 수 있다는 점입니다. **쓰기 절의 변경이 같은 문장의 뒤쪽 읽기에 보입니다.** `MATCH (p) SET p.age = 99 WITH p MATCH (q) RETURN q.age` 는 쓰기 이전 값을 반환했고, `CREATE (:Q) WITH 1 MATCH (x:Q) RETURN count(x)` 는 생성을 조용히 버리면서 0을 반환했습니다. **`ALL`·`ANY`·`NONE`·`SINGLE`이 3값 논리를 따릅니다.** 이제 `any([null] WHERE p)`는 거짓이 아니라 null입니다. 빈 리스트는 공허하게 참인 결과를 그대로 유지합니다. {#218-stricter-validation} ### 엄격해진 검사 아래는 모두 예전에는 성공했고, 대부분의 경우 틀린 답을 내놓던 것들입니다: * `ALTER TABLE`은 더는 그래프 레이블의 모양을 바꿀 수 없습니다. 컬럼을 더하거나 지우는 것, 상속을 바꾸는 것, 레이블 컬럼의 이름을 바꾸는 것입니다. 레이블 DDL이 같은 일을 하며, `enable_graph_ddl`이 그 제한을 풉니다. 그러고도 `id`·`start`·`end`·`properties`의 타입을 바꾸는 것은 거부됩니다. * 레이블에 대한 SQL `MERGE`는 이제 `enable_graph_dml`이 필요합니다. `INSERT`·`UPDATE`·`DELETE`는 이미 그랬습니다. * **행 수준 보안이 이제 그래프 쓰기에 실제로 적용됩니다.** `UPDATE`나 `DELETE` 정책의 `USING` 절이 결코 스캔되지 않는 범위 테이블 항목에 붙어 있어 아무것도 거르지 못했고, `DELETE` 정책이 아예 없는 롤도 행을 지웠습니다. `INSERT` 정책은 엉뚱한 행을 대상으로 검사됐고 간선 생성은 검사된 적이 없어, 행 보안이 걸린 간선 레이블이 모든 삽입을 받아들였습니다. 그래프에 RLS를 기대고 있다면 2.18에서 다시 시험하십시오. 전에는 통과하던 쓰기가 이제 거부됩니다. * `WITH`는 같은 이름을 두 번 담을 수 없고, `ORDER BY` 키는 휘발성 항목으로 계산할 수 없으며, 패턴 변수는 이미 범위에 있는 컬럼과 같은 이름을 쓸 수 없고, 그래프 쓰기는 속성 맵을 스칼라로 바꿔치울 수 없으며, `CALL` 서브쿼리는 바깥에 이미 묶인 이름을 반환할 수 없습니다. {#218-diagnostics} ### 진단 그래프 요소에 대한 `count()`, 속성 읽기, 그리고 가변 길이 순회 노드에서 `EXPLAIN` 출력이 바뀌었습니다. 가변 길이 순회 노드는 이제 자기가 따라가는 간선 레이블의 이름을 표시합니다. 계획 텍스트를 비교하는 회귀 기준이 있다면 갱신하십시오. {#218-fixes} ## 수정 크래시 15종을 고쳤습니다. 그중 몇은 읽기 권한밖에 없는 사용자도 닿을 수 있었고, 그 인스턴스의 모든 세션을 함께 쓰러뜨렸습니다: * `head('{}'::text[])` 가 *계획을 짜는 도중* 서버를 죽였습니다. * `RETURN cardinality(['a','b'])` 가 파싱 시점에 죽었습니다. * `MATCH ()-[r:e*1..2 = 5]->()` — 가변 길이 간선에 맵이 아닌 조건을 붙인 것입니다. * `CREATE PROPERTY INDEX ON doc (tags[0])` 입니다. * 리스트 컴프리헨션 안의 집계입니다. * 64자를 넘는 리스트 원소가 고정 버퍼를 넘어갔습니다. 오류가 아니라 틀린 답을 내놓고 있던 수정이 넷 더 있습니다. 2.17.0에서 `vertex`의 순서와 `edge`의 같음 판정이 틀려서, 정점에 대한 `<`·`<=`·`>`·`>=`와 간선에 대한 `=`가 말하는 그대로를 뜻하지 않았습니다. `min(jsonb)`와 `max(jsonb)`는 아무 원소나 반환했습니다. `DELETE`는 자기가 지운 요소에 대해 빈 값을 반환했고, 그것에 묶인 다른 변수도 비웠습니다. 그리고 그래프 요소에 대한 `x IN `가 동일성이 아니라 직렬화 전체를 비교했습니다. 이 릴리스 안에서 PostgreSQL 18 회귀도 하나 고쳤습니다. 속성 읽기에 대한 `DISTINCT`·`GROUP BY`· `ORDER BY`·`min()`·`max()`가 "could not determine which collation to use"로 실패했습니다. 이제 그런 키는 데이터베이스 기본값, 즉 jsonb 자신이 쓰는 것과 같은 콜레이션을 취하므로 결과는 그대로입니다. {#218-not-in-this-release} ## 이 릴리스에 없는 것 문의가 있었으므로 분명히 적어 둡니다: * **`CALL { }` 안의 쓰기 절.** 파서가 서브쿼리 본문의 `CREATE`·`SET`과 그 밖의 쓰기 절을 거부합니다. `CALL { } IN TRANSACTIONS`는 없습니다. * **GQL의 세션과 스키마** — `SESSION_SET`·`USE` — 그리고 타입 있는 그래프와 그래프 타입입니다. * **`CREATE OR REPLACE CONSTRAINT`.** 파싱은 되지만 그다음 거부됩니다. * **저장되지 않거나 생성되지 않는 승격 컬럼.** 승격 컬럼은 `GENERATED … STORED`여야 합니다.