문제 상황 - relation "" does not exist


error returned from database: relation "ezyweb_user" does not exist 오류가 잡히는데,
ezyweb_user는 존재하는 릴레이션이며 cargo run으로 실행 후 DB에 데이터도 잘 저장된다.
하지만 rust-analyzer는 계속 존재하지 않는 릴레이션이라며 오류를 표시하는데 여간 불편한 게 아니다.

이 오류를 해결하고 싶어서 어제, 오늘 꼬박 이틀을 잡고 있었는데 아직은 해결하지 못했다.
문제가 없는 문제를 해결하려고 하는 건가 싶은 생각마저 들게 한다.
우선 이럴 땐 뭐가 잘못됐는지 점검해 볼 수 있는 방법들을 알아보자.
점검하다보면 문제의 원인을 발견하게 될 지도 모른다.
근데 결론부터 말하자면 아래 방법들로 모두 점검해봤는데 아직까진 전혀 문제를 발견하지 못했다.
그래서 일단 지금은 rust-analyzer의 버그라는 결론을 내린 상태다.
만약에 추가 의견이 있다면 댓글로 함께 배워보는 시간을 가져보겠습니다.
1). env파일에 DATABASE_URL이 제대로 설정됐는지 확인하기

나는 postgreSQL로 ezytutor_web_ssr이라는 db를 만들었고, 생성한 유저명과 비밀번호가 잘 입력돼있다.
즉, 환경변수 내용은 문제없다.
그럼 환경변수를 이용하는 부분은 어떨까

main함수에서 해당 환경변수를 이용해서 db에 연결하고 있는데, 따로 문제 될 부분은 보이지 않는다.
2) ezytutor_web 릴레이션이 존재하는지 확인하기

ezytutor_web_ssr 데이터베이스 내 생성해 두었던 유저 zzaekkii로 접속해, \d 테이블(릴레이션) 목록을 조회해 보았다.
몇몇 한글이 깨져서 표시되긴 하지만, ezyweb_user 테이블은 잘 생성되어 있는 게 확인된다.
ezytutor_web_ssr=> \d
List of relations
Schema | Name | Type | Owner
--------+-------------+-------+----------
public | ezyweb_user | table | zzaekkii
(1 row)
public 스키마에 있는지도 확인해 보자.
ezytutor_web_ssr=> \d public.ezyweb_user
Table "public.ezyweb_user"
Column | Type | Collation | Nullable | Default
---------------+-----------------------+-----------+----------+---------
user_password | character(100) | | not null |
Indexes:
"ezyweb_user_pkey" PRIMARY KEY, btree (user_id)
ezytutor_web_ssr=> SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';
table_name
-------------
ezyweb_user
(1 row)
ezyweb_user 릴레이션이 public 스키마에 확실하게 존재한다는 것이 밝혀졌지만,
그래도 pgAdmin14 GUI툴로 직접 확인해 보기로 했다.
3) pgAdmin14 GUI 도구로 테이블 존재 확인하기

ezytutor_web_ssr 데이터베이스 아래 - public 스키마 아래 - ezyweb_user 테이블이 존재하는 게 확인된다.
그리고 테이블 내에 필드 3개(user_id, tutor_id, user_password)도 잘 생성된 게 보인다.
ezyweb_user 테이블 생성과 필드 등록은 스크립트 파일을 psql(postgreSQL 전용 쉘)에서 실행했다.
이로써 ezytutor_web_ssr 데이터베이스는 확실하게 존재한다는 걸 확인했고,
이 DB에 정상적으로 연결이 됐다면, public 스키마의 ezyweb_user 테이블도 제대로 인식돼야 정상이다.
하지만 rust-analyzer에선 ezyweb_user를 인식하지 못하고 있다.
이럴 땐 테이블을 지웠다가 다시 생성해 볼 수 있다.
4) 스크립트 파일을 재실행해서 기존 테이블 DROP 후, 다시 CREATE

이전에 생성된 ezyweb_user가 있다면 DROP하는 쿼리가 들어있고,
3개 필드가 들어간 ezyweb_user 테이블을 생성하는 쿼리가 담긴 스크립트 파일을 psql 내에서 실행했다.
아래 터미널을 보면 DROP TABLE, CREATE TABLE을 확인할 수 있다.
정상적으로 삭제 후, 재생성이 됐다는 의미다.

그렇지만 rust-analyzer는 여전히 오류를 표시하고 있다.
5) 쿼리에 public 스키마를 직접 명시해주기

그래도 문제는 여전했다.
6) "" 큰따옴표를 이용해 대소문자를 확실하게 구분해주기

이것도 문제를 해결해주진 못 했다.
사실 구글에 relation does not exist 오류를 찾아보면 대부분 이름 대소문자 문제라 이렇게 해결한다.
그렇지만 내 경우엔 ezyweb_user로 모두 소문자를 쓰는 상황이고, "ezyweb_user" 큰따옴표로 구분해도 소용없었다.
7) Cargo.toml에 관련 의존성 확인하기

.env 환경변수 파일을 사용하기 위한 dotenv 크레이트도 최신버전으로 문제없다.
옆에 사이렌 표시는 이번 문제와 관련이 없으니 넘어가자.
그리고 sqlx 크레이트도 현재 사용하는 postgres를 제대로 추가되어 있고, 최신버전이다.
dependency문제는 아닌 것 같다.
8) 정의된 구조체 필드명과 일치하는지 확인하기
오류 내용은 릴레이션 존재 여부였긴 하지만, 필드 매치도 문제없는지 재확인해보자.

sqlx::FromRow 크레이트도 유도되어 있고, 세 가지 필드 모두 일치한다.
model에도 문제는 없다.
점검 중간 평가 -> DB 문제는 아니다
VScode를 껐다 키는 것도 당연히 해봤지만 달라지는 건 없었다.
이쯤 점검해 봤을 때, db 관련 문제는 아니라는 게 확실해진다.
그렇다면 rust-analyzer가 저런 오류를 표시하는 이유가 뭘까?
sqlx::query_as!() 과정에서 오류가 발생하고 있는 건가도 추측해 볼 수 있다.

지금 발생하고 있는 에러는 클라이언트 서버를 작성하는 중에 발생한 에러인데,
며칠 전에 만들었던 백엔드 서버 코드에서의 query_as!()엔 아무런 문제가 없었기 때문에 조금 혼란스럽긴 했다.
그래서 원인으로는 크게 고려하고 있지 않았었다.
문제가 없었던 백엔드 서버 코드에서의 것들과 클라이언트 서버 측의 것들을 모두 비교해 봤지만,
딱히 이렇다 할 문제점을 찾지는 못했다.
이렇게 되면 소거법으로 이제 남은 건 rust-analyzer 뿐이다.
rust-analyzer가 이렇게 버그가 많은 툴이었나?
실행하면 정상적으로 돌아가는데 계속 오류로 표시되니 그냥 넘어가기도 어렵다.
rust-analyzer가 문제를 잡아내는 메커니즘은 어떻게 이뤄질까?
reddit에서 발견한 비슷한 상황 - sqlx::query 문제
https://www.reddit.com/r/rust/comments/13wuyi0/i_cannot_get_sqlxquery_to_work_with_my_local/
From the rust community on Reddit
Explore this post and more from the rust community
www.reddit.com
작성자도 나와 같은 오류는 아니지만 sqlx::query 관련 문제를 겪은 걸로 보이는데,
컴파일 타임과 런타임 사이의 괴리가 있는 것 같다.
작성자가 해결한 방법은 내 경우와 맞지 않았다.
오류는 여전하지만, 실행하면 잘 동작한다

localhost:8080/으로 접속하면 강사 등록 회원가입 페이지에 잘 들어가진다.


양식에 맞게 내용을 잘 채워준 뒤 sign-up 버튼을 클릭하면,

가입이 성공했을 때 뜨는 문구와 고유 tutor_id 값이 표시되는 걸 확인할 수 있다.
책에 나와있는 프로젝트 구조가 희한한데, 책이 끝나고 리팩터링할 예정이다.
이 책은 ssr 방식 렌더링을 담당하는 클라이언트(8080) 서버 하나와 백엔드(3000) 서버를 따로 두고 있다.
그리고 백엔드 서버에 postgreSQL DB를 두고, 클라이언트 서버에도 DB를 둔다.
근데 백 DB엔 강사 소개 같은 필드를 넣으면서, 클라이언트 DB엔 비밀번호(?!)를 넣는다.
심지어 비밀번호 해싱도 클라이언트에서 처리하는데 왜 이렇게 설계했는지 정말 모르겠다.
아직 제대로 된 개발 경험이 없어서 잘 모르는 상태라 더 배워야 이해할 수 있을 것 같다.
내가 모르는 건지, 책이 이상한 건지
여하튼 7챕터부터(SSR) 혼란스러워져서 일단 마저 끝내고 직접 바꾸기로 했다.
By the way,
클라이언트 가입 폼에서 양식에 맞게 올바른 데이터를 전송하면
그 데이터(강사 상세정보)를 백엔드 서버 db에 저장하고 고유 tutor_id 값을 생성한다.
그리고 클라이언트에서 해당 tutor_id 값을 받아오는 API를 호출해서
클라이언트 템플릿 엔진(Tera)에 렌더링한 뒤 값을 표시한다.


클라이언트 서버 db엔 사용자 관리를 위해 아이디, 비밀번호, 고유 강사번호를 저장하는데,
지금 계속 오류를 띄우는 이 클라이언트 서버의 db엔 과연 데이터가 제대로 들어갔을까?

SELECT * FROM ezyweb_user 쿼리를 날려보면 위처럼 데이터가 잘 저장된 걸 볼 수 있다.
그럼 로직에도 문제가 없고 DB에 데이터도 정상적으로 저장되는데,
rust-analyzer는 왜 ezyweb_user 릴레이션이 존재하지 않는다는 오류를 표시하는 걸까?
그게 앞으로의 인류가 해결해야 할 과제라고 생각한ㄷ.. 에휴~
그 이유에 대해 내 나름대로 조사하고 고민해보았다.
이상이 없는데, rust-analyzer는 왜 오류를 감지할까?
rust-analyzer는 컴파일 타임에 코드를 분석하는 정적 코드 분석 도구다.
특성상 컴파일 시간이 오래걸리는 rust에 정적으로 오류나 비효율적인 부분을 알려주는 도구는 정말 편리하다.
그러나 컴파일 타임에 분석하다보니, 런타임에 동작하는 코드에 대해서는 예상 결과가 일치하지 않을 수 있다.
이번 상황이 그렇다고 볼 수 있다.
sqlx 매크로(sqlx::query_as!() 등)는 DB에 직접 연결해서 쿼리를 검증한다.
하지만 rust-analyzer는 컴파일 타임에 DB 스키마와 쿼리간 상호작용을 검증하는데,
실제로 DB 쿼리를 실행하는 것이 아니고 메타 데이터와 구문 분석으로 코드를 확인한다.
컴파일 타임에 실시간으로 DB 연결해서 확인하는 게 아니기 때문에 analyzer는 오류로 판단할 수 있다.
그래서 실제로 애플리케이션을 실행했을 때 문제가 없더라도 rust-analyzer에서는 오류로 인식할 수 있다.
간략히 말하면, sqlx처럼 동적으로 연결하는 코드는 rust-analyzer가 완벽하게 분석하지 못할 수도 있다는 의미다.
이번 상황처럼 코드 상 문제가 없더라도 relation does not exist 오류로 인식할 수 있다.
아쉽지만 정적 분석 도구인 rust-analyzer의 태생적 한계인 것 같다.
그렇지만 그건 그거고, 오류 계속 뜨는 건 너무 불편하니까 오류가 안 뜨게라도 어떻게 해봐야지
문제 해결법 조사

지금까지 글을 작성하면서 그렇구나, rust-analyzer의 태생적 한계구나..
어쩔 수 없는 거구나하고 글을 마무리하려했는데
아무리 오작동이라지만 오류를 무시한다는 게 너무 찝찝해서 조금 더 조사해보기로 했다.
GPT의 답변 - "offline" feature로 sqlx 컴파일 타임 쿼리 검증 활성화

오호.. sqlx 크레이트에 offline이라는 feature가 있다고 한다.
독스엔 나와있지 않아서 몰랐는데, offline이라는 feature를 사용하면 컴파일 타임에 검사할 수 있다고 한다!
https://docs.rs/sqlx/latest/sqlx/?search=offline
sqlx - Rust
The async SQL toolkit for Rust, built with ❤️ by the LaunchBadge team. See our README to get started or browse our example projects. Have a question? Check our FAQ or open a discussion. §Runtime Support SQLx supports both the Tokio and async-std runti
docs.rs

Cargo.toml 파일에서 sqlx 크레이트에 "offline" feature 의존성을 추가한다.

오?? 진짜 에러 표시가 말끔하게 사라졌다?
응? 그런데 rust-analyzer가 할 말이 잔뜩 있어 보이는데
클릭해보면
cargo check failed to start: Cargo watcher failed, the command produced no valid metadata (exit code: ExitStatus(ExitStatus(101))): error: failed to select a version for sqlx. ... required by package tutor-web-app-ssr v0.1.0 (D:\Rust\EzyTutors\ezytutors\tutor-web-app-ssr) versions that meet the requirements ^0.8.2 (locked to 0.8.2) are: 0.8.2 the package tutor-web-app-ssr depends on sqlx, with features: offline but sqlx does not have these features. failed to select a version for sqlx which could resolve this conflict Failed to read Cargo metadata with dependencies for D:\Rust\EzyTutors\ezytutors\Cargo.toml: Failed to run "C:\\Users\\cjy13\\.cargo\\bin\\cargo.exe" "metadata" "--format-version" "1" "--manifest-path" "D:\\Rust\\EzyTutors\\ezytutors\\Cargo.toml" "--filter-platform" "x86_64-pc-windows-msvc": cargo metadata exited with an error: error: failed to select a version for sqlx. ... required by package tutor-web-app-ssr v0.1.0 (D:\Rust\EzyTutors\ezytutors\tutor-web-app-ssr)

역시나 sqlx에 offline같은 feature는 없다는 말이잖아 젠장~
진짜 있는 기능인 것처럼 자세히 설명해놓고! 이정도면 sqlx 개발자도 깜빡 속겠네!
GPT 함부로 믿지 마세요 여러분 체질이라는 게 바뀝니다.
rust-analyzer Reload Workspace로 캐시 삭제하기

rust-analyzer에서 손상된 캐시를 가진 경우 문제가 발생할 수 있다는 정보를 얻어 캐시를 비워보기로 했다.
VScode 기준 rust-analyzer에 clear cache하려면, 하단 탭에서 Reload Workspace를 클릭하면 된다.
오~ unused import 경고 2개 빼고 아까 계속 뜨던 relation does not exist 문제는 사라졌는데?
인 줄 알았는데, 또..

cargo sqlx prepare로 .sqlx 메타 데이터 생성하기
아까 rust-analyzer는 메타 데이터와 구문 분석으로 코드를 검증한다고 이야기했다.
그럼 메타 데이터를 주면 되잖아?

cargo sqlx prepare 명령어로 메타데이터를 생성하면 옆의 파일탐색기에 .sqlx 폴더가 추가된 걸 볼 수 있다.
하지만 메타데이터를 만들어줬는데도 아직 relation "ezyweb_user" does not exist가 발생하고 있다.
솔직히 이 메타데이터만 만들어주면 컴파일타임에도 SQL 쿼리를 검증할 수 있을 거라 생각했는데.
이게 안 되네
cargo sqlx migrate로 DB 스키마 업데이트하기
cargo sqlx 명령어 중엔 migrate라고 스키마를 업데이트하는 migration 파일을 관리할 수 있는데,
이전에 /dbscripts/user.sql처럼 직접 스크립트로 넣었던 쿼리를 migrate 명령어로 관리해볼 생각이다.
sqlx 명령어로 스키마를 업데이트하면 rust-analyzer도 인식하지 않을까??
PS D:\Rust\EzyTutors\ezytutors\tutor-web-app-ssr> cargo sqlx migrate
Group of commands for creating and running migrations
Usage: cargo sqlx migrate <COMMAND>
Commands:
add Create a new migration with the given description
run Run all pending migrations
revert Revert the latest migration with a down file
info List all available migrations
build-script Generate a `build.rs` to trigger recompilation when a new migration is added
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
cargo sqlx add로 마이그레이션 폴더를 만들고, 내부에 테이블 생성 SQL 쿼리를 만들어 run 한다면?
그럼 DB 스키마를 최신 상태로 만들어줄 수 있으니까 rust-analyzer도 인식하지 않을까?

이번에야말로 정말 해결할 수 있을 줄 알았는데,
완전 논리적인 접근법이었다고 생각했는데,
왜~~
왜 안 돼~~~?
psql에서 ezyweb_user가 제대로 만들어졌는지도 바로 확인해보자.

너무 잘 만들어져있잖아 젠장,,
정녕 해결할 수 없는 문제란 말인가?
rust-analyzer 최신버전에서 일어나는 현상이다?
https://github.com/rust-lang/rust-analyzer/issues/17300
Regression: Diagnostics not clearing after fix and save · Issue #17300 · rust-lang/rust-analyzer
This seems to be a regression from the last released version of the rust-analyzer extension. When the file is saved, diagnostics will appear, but then if problem is fixed and saved again, the diagn...
github.com
오류를 수정한 뒤에도 rust-analyzer에서 계속 같은 오류를 표시하는 버그가 있다고 한다.
내 경우엔 오류가 없었는데도 잘못 오류를 표시하고 있긴 하지만 이건가? 싶어서 읽어보았는데,
https://github.com/rust-lang/rust-analyzer/pull/17302
fix: fix diagnostics clearing when flychecks run per-workspace by mladedav · Pull Request #17302 · rust-lang/rust-analyzer
This might be causing #17300 or it's a different bug with the same functionality. I wonder if the decision to clear diagnostics should stay in the main loop or maybe the flycheck itself should ...
github.com
이미 해결된 문제라서, 이것때문에 일어나는 일은 아닌 것 같았다.
요약 & 결론
rust로 클라이언트 db 접근 함수 로직을 sqlx 매크로를 이용해서 쿼리를 작성하다가,
분명히 생성한 테이블임에도 불구하고 rust-analyzer에 의해 relation does not exist 오류가 표시됐다.
DB 관련 문제인가 싶어 관련된 부분을 모두 확인해보았지만 이상이 없었다.
DB와 relation은 정상적으로 생성돼 있었다 => DB 문제는 아님.
애플리케이션도 정상적으로 실행되고, 데이터를 넣어도 정상적으로 저장되고 조회된다.
cargo clear / update / check / build / run 역시 모두 문제 없다 => 코드 문제도 아님.
Reload Workspace로 rust-analyzer 내부 캐시도 비워봤고 => 캐시 손상 문제 아님.
cargo sqlx prepare로 .sqlx 메타 데이터도 생성해봤지만 소용없었으며 => 메타 데이터 문제 아님.
cargo sqlx migrate로 DB 스키마를 최신으로 업데이트 했는데도 안 됐다 => rust-analyzer가 문제임.
결론은 컴파일 타임에 정적으로 분석하는 도구인 rust-analyzer의 태생적인 한계로
sqlx 매크로와 같은 런타임에 동적으로 실행되는 코드에 대해서는 정확하게 인식하지 못 하는 것 같다.
백엔드에선 정상 동작했으니 무조건 안 되는 것도 아닌 것 같다.
할 수 있는 건 다 해봤는데 결국엔 해결할 수 없었다.
그래도 미련은 없다 이제~
애초에 실행도 잘 되고 동작에 문제도 없는데, 왜 이런 허상 오류를 해결하지 못 한단 말인가!
내 이틀하고도 반 어디갔어?
내 주말,,
'Rust 연구 노트 > "EzyTutors" 프로젝트' 카테고리의 다른 글
[오류 해결] failed to run custom build command for openssl-sys (5) | 2024.09.06 |
---|---|
[Rust] Actix를 사용해서 RESTful API 만들기 (5) | 2024.08.25 |
[Rust/도서 리뷰] <Rust 서버, 서비스, 앱 만들기> (0) | 2024.06.30 |