소스 제어에 저장 프로시저/DB 스키마
선택한 소스 제어 시스템에서 저장 프로시저와 데이터베이스 스키마를 추적합니까?
변경(테이블 추가, 저장된 프로시저 업데이트, 변경 사항을 소스 제어로 가져오는 방법)
우리는 직장에서 SQL Server를 사용하고 있고, 저는 버전 관리를 위해 다크를 사용하기 시작했습니다. 하지만 일반적인 전략과 유용한 도구에 대해 알고 싶습니다.
편집: 와, 좋은 제안을 해주셔서 감사합니다, 여러분!두 개 이상의 "승인된 답변"을 선택할 수 있으면 좋겠습니다!
모든 저장 프로시저 및 스키마 변경 사항을 포함하여 모든 내용을 스크립팅하도록 선택합니다.이제 wyswyg 도구와 화려한 '동기화' 프로그램이 필요하지 않습니다.
스키마 변경은 간단합니다. 모든 스키마 및 데이터 변경을 포함하여 해당 버전에 대한 단일 파일을 만들고 유지 관리하기만 하면 됩니다.버전 x에서 x+1로 변환 스크립트가 됩니다.그런 다음 프로덕션 백업에 대해 이를 실행하고 이를 '일별 빌드'에 통합하여 오류 없이 작동하는지 확인할 수 있습니다.이미 작성된 스키마/데이터 로드 SQL을 변경하거나 삭제하지 않는 것이 중요합니다. 나중에 작성된 SQL이 손상될 수 있기 때문입니다.
-- change #1234
ALTER TABLE asdf ADD COLUMN MyNewID INT
GO
-- change #5678
ALTER TABLE asdf DROP COLUMN SomeOtherID
GO
저장 프로시저의 경우 저장 프로시저당 단일 파일을 선택하고 드롭/생성 양식을 사용합니다.모든 저장 프로시저는 배포 시 다시 생성됩니다.단점은 소스 제어 외부에서 변경이 수행된 경우 변경 내용이 손실된다는 것입니다.동시에 모든 코드에 해당되지만 DBA는 이를 알아야 합니다.이렇게 하면 팀 외부의 사용자가 저장 프로시저를 조작하는 것을 막을 수 있습니다. 업그레이드 시 변경 사항이 손실되기 때문입니다.
SQL Server를 사용하는 구문은 다음과 같습니다.
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[usp_MyProc]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [usp_MyProc]
GO
CREATE PROCEDURE [usp_MyProc]
(
@UserID INT
)
AS
SET NOCOUNT ON
-- stored procedure logic.
SET NOCOUNT OFF
GO
이제는 모든 개별 파일을 수집하고 전체 업데이트 세트(단일 스크립트)로 새 파일을 생성하는 유틸리티 프로그램을 작성하는 것이 전부입니다.먼저 스키마 변경사항을 추가한 후 디렉토리 구조를 재귀화하고 저장된 모든 프로시저 파일을 포함하여 이 작업을 수행합니다.
모든 것을 스크립팅하는 것의 장점으로, 당신은 SQL을 읽고 쓰는 것을 훨씬 더 잘하게 될 것입니다.이 전체 프로세스를 보다 정교하게 만들 수도 있지만, 이는 특별한 소프트웨어 없이 모든 SQL을 소스로 제어하는 기본 형식입니다.
부록:Rick은 DROP/CREATE를 사용하여 저장 프로시저에 대한 사용 권한을 잃게 되므로 특정 사용 권한을 다시 활성화하기 위해 다른 스크립트를 작성해야 할 수도 있습니다.이 권한 스크립트가 마지막으로 실행됩니다.우리의 경험은 ALTER 대 DROP/CREATE 의미론에서 더 많은 문제를 발견했습니다.YMMV
Visual Studio에서 "데이터베이스 프로젝트"를 생성하여 sQL 코드를 작성 및 관리하고 나머지 솔루션과 함께 프로젝트를 버전 관리 상태로 유지할 수 있습니다.
마지막 작업에서 사용한 솔루션은 소스 제어에 추가된 스크립트에 번호를 매기는 것이었습니다.
01.UserTable.sql 생성
02 테이블 02를 .사용자 테이블 채우기
03.AlterUserTable.sql
04.CreateOrderTable.sql
스크립트를 실행할 순서를 항상 알고 있으므로 스크립트 #1을 수정할 경우 발생할 수 있는 데이터 무결성 문제를 관리하지 않아도 됩니다(#2의 INSERT가 실패할 수 있음).
SQL Server에서 스크립트를 삭제/생성할 때 주의해야 할 한 가지 사항은 개체 수준 사용 권한이 손실된다는 것입니다.대신 ALTER 스크립트를 사용하여 해당 권한을 유지하도록 표준을 변경했습니다.
개체를 삭제하면 sp_depends에서 사용하는 종속성 레코드가 삭제되고 개체를 생성하면 해당 개체에 대한 종속성만 생성된다는 점과 같은 몇 가지 다른 주의 사항이 있습니다.따라서 보기를 삭제/생성하면 sp_depends는 해당 보기를 참조하는 개체를 더 이상 알지 못합니다.
교훈적으로 ALTER 스크립트를 사용합니다.
저는 로버트 폴슨의 관행에 동의합니다.이는 여러분이 그러한 관행을 고수할 책임과 규율을 가진 개발 팀을 통제하고 있다고 가정하는 것입니다.
이를 팀에 "강제"하기 위해 Visual Studio Team Edition for Database Professionals의 데이터베이스 프로젝트를 하나 이상 유지 관리합니다.솔루션의 다른 프로젝트와 마찬가지로 데이터베이스 프로젝트도 버전별 제어를 받습니다.데이터베이스의 모든 내용을 유지관리 가능한 덩어리로 분할하여 팀을 "훈련"하는 것이 자연스러운 개발 프로세스가 됩니다.
물론 Visual Studio 프로젝트이기 때문에 완벽에 가까운 것은 아닙니다.여러분을 좌절시키거나 혼란스럽게 할 수 있는 많은 기괴한 일들을 마주치게 될 것입니다.프로젝트가 작업을 완료하기 전에 프로젝트가 어떻게 작동하는지를 상당히 이해해야 합니다.예를 들어 다음과 같습니다.
- CSV 파일에서 데이터를 배포합니다.
- 빌드 유형을 기반으로 한 테스트 데이터의 선택적 배포.
- Visual Studio는 특정 유형의 CLR 어셈블리가 내장된 데이터베이스와 비교할 때 충돌합니다.
- SQL 사용자와 Active Directory 사용자 등 서로 다른 인증 체계를 구현하는 테스트/운영 데이터베이스 간의 차별화 수단이 없습니다.
그러나 데이터베이스 개체의 버전을 지정하지 않은 팀의 경우 이를 시작하는 것이 좋습니다.다른 유명한 대안으로는 Red Gate의 SQL Server 제품군이 있는데, 이 제품을 사용하는 대부분의 사람들은 이 제품이 Microsoft 제품보다 우수하다고 생각합니다.
저장된 절차를 포함하여 데이터베이스를 자동으로 설정하는 스크립트를 작성해야 한다고 생각합니다.그런 다음 이 스크립트를 소스 제어에 배치해야 합니다.
제 경험에 비추어 볼 때 여러 가지 관점이 있습니다.Oracle 환경에서는 DDL 스크립트를 "생성"하여 모든 것을 관리했습니다.Hockley가 언급했듯이, 각 객체에 대해 하나의 스크립트입니다.개체를 변경해야 하는 경우 해당 DDL 스크립트가 수정됩니다.원하는 환경에 현재 DB 빌드를 배포할 수 있도록 모든 개체 스크립트를 호출하는 래퍼 스크립트가 하나 있습니다.이것은 주 코어 생성용입니다.
실시간 애플리케이션에서는 새 열이 필요한 새 빌드를 푸시할 때마다 테이블을 떨어뜨려 새로 만들지 않습니다.ALTER 스크립트를 수행하고 열을 추가합니다.따라서 이러한 종류의 변경이 필요할 때마다 항상 두 가지 작업이 수행됩니다. 1) 변경 사항을 작성하고 2) 변경 사항을 반영하도록 코어 생성 DDL을 업데이트하는 것입니다.둘 다 소스 제어에 들어가지만 단일 변경 스크립트는 델타를 적용하는 데만 사용되기 때문에 일시적인 시간 변경에 가깝습니다.
ERWin과 같은 도구를 사용하여 모델을 업데이트하고 DDL을 포워드 생성할 수도 있지만, 제가 아는 대부분의 DBA는 원하는 방식으로 스크립트를 생성할 수 있는 모델링 도구를 신뢰하지 않습니다.또한 ERWin을 사용하여 코어 DDL 스크립트를 정기적으로 모델로 리버스 엔지니어링할 수도 있지만, 이를 올바르게 보이도록 하는 것은 매우 어려운 일입니다.
마이크로소프트 세계에서 우리는 비슷한 전략을 사용했지만, 스크립트와 델타를 관리하는 데 도움이 되는 Red Gate 제품을 사용했습니다.여전히 스크립트를 소스 제어에 둡니다.개체당 하나의 스크립트(테이블, 저장 프로시저 등)만 사용할 수 있습니다.처음에는 일부 DBA가 스크립트를 사용하는 대신 SQL 서버 GUI를 사용하여 개체를 관리하는 것을 선호했습니다.그러나 이로 인해 기업이 성장함에 따라 기업을 지속적으로 관리하기가 매우 어려웠습니다.
DDL이 소스 제어 상태에 있는 경우 빌드 도구(일반적으로 개미)를 사용하여 배포 스크립트를 작성하는 것은 사소한 일입니다.
이를 위한 가장 쉽고, 빠르고, 안전한 방법은 RedGate의 SQL Source Control을 사용하는 것입니다.스크립트로 작성되어 몇 분 만에 저장소에 저장됩니다.저는 레드게이트가 제품을 손실 리더로 간주하여 더 널리 사용되기를 바랄 뿐입니다.
위의 Robert Paulson과 유사하게, 우리 조직은 데이터베이스를 소스 제어 하에 유지합니다.그러나 우리의 차이점은 우리가 가지고 있는 스크립트의 수를 제한하려고 한다는 것입니다.
새로운 프로젝트에는 정해진 절차가 있습니다.버전 1에는 스키마 생성 스크립트, 저장 프로시저 생성 스크립트 및 초기 데이터 로드 생성 스크립트가 있습니다.모든 프로시저는 하나의 대용량 파일에 보관됩니다.Enterprise Library를 사용하는 경우 로깅을 위한 생성 스크립트의 복사본이 포함됩니다. ASP.NET 프로젝트가 ASP.NET 응용 프로그램 프레임워크(인증, 개인 설정 등)를 사용하는 경우에는 해당 스크립트도 포함됩니다. (Microsoft의 도구에서 생성한 다음 여러 사이트에서 복제 가능한 방식으로 작동할 때까지 수정했습니다.재미는 없지만 귀중한 시간 투자입니다.)
마법의 CTRL+F를 사용하여 원하는 proc를 찾습니다. :) (SQL Management Studio에 VS와 같은 코드 탐색 기능이 있으면 좋겠습니다.한숨!)
이후 버전의 경우 일반적으로 upgradeSchema, upgradeProc 및/또는 updateDate 스크립트가 있습니다.스키마 업데이트의 경우 가능한 한 테이블을 변경하여 필요에 따라 새 테이블을 만듭니다.proc 업데이트의 경우 Drop 및 CREATE를 수행합니다.
이러한 접근법으로 인해 한 가지 주름이 나타납니다.데이터베이스를 쉽게 생성할 수 있으며, 현재 DB 버전에서 새로운 데이터베이스를 쉽게 업데이트할 수 있습니다.그러나 DB/schema/proc 변경 사항이 액세스에 사용되는 코드와 완전히 동기화되도록 하기 위해 DAL 생성(일반적으로 현재는 SubSonic에서 수행)에 주의해야 합니다.그러나 빌드 경로에는 SubSonic DAL을 생성하는 배치 파일이 있으므로 DAL 코드를 확인하고 배치 파일을 다시 실행한 다음 스키마 및/또는 프로세스가 변경될 때마다 모든 것을 다시 확인하는 것이 당사의 SOP입니다.(물론 이것은 소스 빌드를 트리거하여 공유 종속성을 적절한 DLL로 업데이트합니다...)
과거에는 제품의 각 릴리스에 대해 데이터베이스 변경사항이 항상 스크립트로 작성되어 작업 중인 릴리스에 저장되도록 데이터베이스 변경사항 소스를 제어해 왔습니다.작성 프로세스는 각 "응용프로그램"에 대한 현재 버전을 저장한 데이터베이스의 테이블을 기준으로 데이터베이스를 현재 버전으로 자동으로 가져옵니다.그런 다음 작성한 사용자 지정 .net 유틸리티 응용 프로그램을 실행하여 데이터베이스의 현재 버전을 확인하고 스크립트의 접두사 번호 순서대로 새 스크립트를 실행합니다.그런 다음 유닛 테스트를 실행하여 모든 것이 양호한지 확인했습니다.
다음과 같이 소스 제어에 스크립트를 저장합니다(아래 폴더 구조).
테이블과 저장 프로시저에 대한 현재의 명명 규칙에 약간 익숙치 않습니다. 제 예를 들어보면...
뿌리]
프로그램응용프로그램]
버전]
[본문]
\colons\colons
응용 프로그램을(를) 참조하십시오.
1.2.1\
001.파일 Create.sqlCreate.sql
002.내 다른 테이블.Create.sql
100.dbo.usp.파일.sqlGetAllNewStuff.sql
애플리케이션 및 버전을 고려한 버전 테이블을 사용하면 애플리케이션이 주간 프로덕션 백업을 복원하고 현재 버전 이후 데이터베이스에 필요한 모든 스크립트를 실행할 수 있습니다..net을 사용하여 이를 트랜잭션으로 쉽게 패키지화할 수 있었고, 실패한 경우 롤백하여 이메일을 보낼 수 있었습니다. 따라서 릴리스에 잘못된 스크립트가 있음을 알 수 있었습니다.
따라서 모든 개발자는 이를 소스 제어로 유지하여 조정된 릴리스를 통해 데이터베이스에 대해 실행하려는 모든 스크립트가 성공적으로 실행되도록 할 것입니다.
이것은 아마도 당신이 찾던 것보다 더 많은 정보일 것입니다. 하지만 우리에게는 매우 잘 작동했고 구조상 모든 개발자들을 쉽게 참여시킬 수 있었습니다.
릴리스일이 다가오면 운영 팀은 릴리스 정보를 따라 소스 제어에서 스크립트를 가져와 데이터베이스에 대해 패키지를 실행했습니다.야간 빌드 프로세스 중에 사용한 net 응용 프로그램으로, 트랜잭션에서 스크립트를 자동으로 패키징하여 무언가가 실패하면 자동으로 롤백되고 데이터베이스에 영향을 미치지 않습니다.
저장 프로시저는 상단에 삭제/생성 문이 있는 경우 표준을 사용하여 sp당 1개의 파일을 가져옵니다.보기 및 기능은 버전 관리 및 재사용이 용이하도록 자체 파일도 가져옵니다.
스키마는 모두 하나의 스크립트로 시작하여 버전을 변경합니다.
것은 다음과 같은 또는 @
프로시저 저장
수
우리 회사에서는 개별 코드 파일과 마찬가지로 소스 제어의 모든 데이터베이스 항목을 개별 스크립트로 저장하는 경향이 있습니다.변경 내역이 유지되도록 데이터베이스에서 먼저 업데이트된 후 소스 코드 저장소로 마이그레이션됩니다.
두 번째 단계로, 모든 데이터베이스 변경사항이 통합 데이터베이스로 마이그레이트됩니다.이 통합 데이터베이스는 배포 후 운영 데이터베이스의 모양을 정확하게 나타냅니다.또한 현재 운영 상태(또는 마지막 구축)를 나타내는 QA 데이터베이스도 있습니다.통합 데이터베이스에서 모든 변경 사항이 적용되면 스키마 디프 도구(Red Gate의 SQL Diff for SQL Server)를 사용하여 데이터베이스 간에 모든 변경 사항을 마이그레이션하는 스크립트를 생성합니다.
설치 관리자와 쉽게 통합할 수 있는 단일 스크립트를 생성하기 때문에 매우 효과적입니다.개발자들이 종종 겪는 가장 큰 문제는 변경 사항을 통합으로 마이그레이션하는 것을 잊어버리는 것입니다.
우리는 저장 프로시저를 소스 제어로 유지합니다.
모든 스크립트(개체 생성 등)를 작성하고 이러한 스크립트를 소스 제어에 저장합니다.변화는 어떻게 이루어집니까?그것은 일이 어떻게 이루어지는지에 대한 표준적인 관행의 일부입니다.테이블을 추가해야 합니까?CREATE TABLE 스크립트를 작성합니다.저장 프로시저를 업데이트하시겠습니까?저장 프로시저 스크립트를 편집합니다.
개체당 하나의 스크립트를 선호합니다.
프로시저의 경우 스크립트 래퍼가 있는 프로시저를 일반 파일로 작성하고 해당 파일의 변경 사항을 적용합니다.올바르게 적용된 경우 해당 파일을 체크인할 수 있으며 해당 파일에서도 복제할 수 있습니다.
스키마를 변경하려면 스크립트를 체크인하여 점진적으로 변경해야 할 수 있습니다.스크립트를 작성하고 적용한 다음 체크인합니다.그런 다음 프로세스를 작성하여 각 스키마 스크립트를 연속으로 자동으로 적용할 수 있습니다.
우리는 저장 프로시저를 소스 제어로 유지합니다.프로젝트에 폴더를 추가하고 각 SP에 대한 파일을 추가한 후 코드를 수동으로 복사하여 붙여넣는 방식입니다.그래서 SP를 변경할 때 소스 컨트롤의 파일을 수동으로 변경해야 합니다.
저는 사람들이 이것을 자동으로 할 수 있는지 듣고 싶습니다.
소스 제어에 스키마와 저장 프로시저를 유지하는 것이 좋습니다.
저장 프로시저를 버전으로 유지하면 문제가 있는 것으로 판단될 때 해당 프로시저를 롤백할 수 있습니다.
스키마는 당신이 의미하는 바에 따라 덜 명확한 대답입니다.복제 환경(prod/dev/user 등)을 위해 소스 제어에서 테이블을 정의하는 SQL을 유지 관리하는 것이 매우 유용합니다.
우리는 현재 프로젝트에서 대체 접근 방식을 사용하고 있습니다. 소스 제어 하에 DB를 두지 않고 각 릴리스에 도달할 때 변경 사항을 스크립팅하기 위해 데이터베이스 diff 도구를 사용하고 있습니다.
그것은 지금까지 매우 잘 작동하고 있습니다.
우리는 애플리케이션과 관련된 모든 것을 SCM에 저장합니다.DB 스크립트는 일반적으로 자체 프로젝트에 저장되지만 다른 코드와 마찬가지로 처리됩니다.설계, 구현, 테스트, 커밋.
저는 공식적인 디렉토리 구조로 스크립팅하기 위해 작업을 실행합니다.
다음은 배치 파일에서 호출되는 VS2005 코드, 코드 끝에 work.app.config 키를 수행하는 명령줄 프로젝트입니다.
온라인에서 찾은 다른 코드를 기반으로 합니다.설정하기에는 약간 번거롭지만 일단 작동하면 잘 작동합니다.
Imports Microsoft.VisualStudio.SourceSafe.Interop
Imports System
Imports System.Configuration
Module Module1
Dim sourcesafeDataBase As String, sourcesafeUserName As String, sourcesafePassword As String, sourcesafeProjectName As String, fileFolderName As String
Sub Main()
If My.Application.CommandLineArgs.Count > 0 Then
GetSetup()
For Each thisOption As String In My.Application.CommandLineArgs
Select Case thisOption.ToUpper
Case "CHECKIN"
DoCheckIn()
Case "CHECKOUT"
DoCheckOut()
Case Else
DisplayUsage()
End Select
Next
Else
DisplayUsage()
End If
End Sub
Sub DisplayUsage()
Console.Write(System.Environment.NewLine + "Usage: SourceSafeUpdater option" + System.Environment.NewLine + _
"CheckIn - Check in ( and adds any new ) files in the directory specified in .config" + System.Environment.NewLine + _
"CheckOut - Check out all files in the directory specified in .config" + System.Environment.NewLine + System.Environment.NewLine)
End Sub
Sub AddNewItems()
Dim db As New VSSDatabase
db.Open(sourcesafeDataBase, sourcesafeUserName, sourcesafePassword)
Dim Proj As VSSItem
Dim Flags As Integer = VSSFlags.VSSFLAG_DELTAYES + VSSFlags.VSSFLAG_RECURSYES + VSSFlags.VSSFLAG_DELNO
Try
Proj = db.VSSItem(sourcesafeProjectName, False)
Proj.Add(fileFolderName, "", Flags)
Catch ex As Exception
If Not ex.Message.ToString.ToLower.IndexOf("already exists") > 0 Then
Console.Write(ex.Message)
End If
End Try
Proj = Nothing
db = Nothing
End Sub
Sub DoCheckIn()
AddNewItems()
Dim db As New VSSDatabase
db.Open(sourcesafeDataBase, sourcesafeUserName, sourcesafePassword)
Dim Proj As VSSItem
Dim Flags As Integer = VSSFlags.VSSFLAG_DELTAYES + VSSFlags.VSSFLAG_UPDUPDATE + VSSFlags.VSSFLAG_FORCEDIRYES + VSSFlags.VSSFLAG_RECURSYES
Proj = db.VSSItem(sourcesafeProjectName, False)
Proj.Checkin("", fileFolderName, Flags)
Dim File As String
For Each File In My.Computer.FileSystem.GetFiles(fileFolderName)
Try
Proj.Add(fileFolderName + File)
Catch ex As Exception
If Not ex.Message.ToString.ToLower.IndexOf("access code") > 0 Then
Console.Write(ex.Message)
End If
End Try
Next
Proj = Nothing
db = Nothing
End Sub
Sub DoCheckOut()
Dim db As New VSSDatabase
db.Open(sourcesafeDataBase, sourcesafeUserName, sourcesafePassword)
Dim Proj As VSSItem
Dim Flags As Integer = VSSFlags.VSSFLAG_REPREPLACE + VSSFlags.VSSFLAG_RECURSYES
Proj = db.VSSItem(sourcesafeProjectName, False)
Proj.Checkout("", fileFolderName, Flags)
Proj = Nothing
db = Nothing
End Sub
Sub GetSetup()
sourcesafeDataBase = ConfigurationManager.AppSettings("sourcesafeDataBase")
sourcesafeUserName = ConfigurationManager.AppSettings("sourcesafeUserName")
sourcesafePassword = ConfigurationManager.AppSettings("sourcesafePassword")
sourcesafeProjectName = ConfigurationManager.AppSettings("sourcesafeProjectName")
fileFolderName = ConfigurationManager.AppSettings("fileFolderName")
End Sub
End Module
<add key="sourcesafeDataBase" value="C:\wherever\srcsafe.ini"/>
<add key="sourcesafeUserName" value="vssautomateuserid"/>
<add key="sourcesafePassword" value="pw"/>
<add key="sourcesafeProjectName" value="$/where/you/want/it"/>
<add key="fileFolderName" value="d:\yourdirstructure"/>
간편하고 즉각적인 솔루션을 찾고 있는 경우, 당사의 SQL Historic 시스템은 백그라운드 프로세스를 사용하여 DDL 변경 사항을 TFS 또는 SVN으로 자동 동기화하여 데이터베이스에서 변경 사항을 적용하는 모든 사용자에게 투명하게 제공합니다.제 경험으로 볼 때, 큰 문제는 서버에서 변경된 내용으로 코드를 소스 제어 상태로 유지하는 것입니다. 일반적으로 워크플로우를 변경하려면 사용자(개발자, 심지어!)에게 의존해야 하고 서버에서 이미 변경된 내용을 확인해야 하는 경우가 많기 때문입니다.기계에 그런 부담을 주는 것은 모든 사람들의 삶을 더 쉽게 만듭니다.
언급URL : https://stackoverflow.com/questions/77172/stored-procedures-db-schema-in-source-control
'source' 카테고리의 다른 글
I18n in Spring 부츠 + Thymeleaf (0) | 2023.07.03 |
---|---|
Flooth의 Firestore에서 단일 문서 쿼리(cloud_firestore Plugin) (0) | 2023.07.03 |
Oracle(ORA-02270): 이 열 목록 오류에 대해 일치하는 고유 또는 기본 키가 없습니다. (0) | 2023.07.03 |
mongodb에 어떤 스토리지 엔진이 사용되는지 어떻게 알 수 있습니까? (0) | 2023.06.28 |
ASP.NET MVC 보기 결과 대 부분 보기 결과 (0) | 2023.06.28 |