{"id":51123608,"url":"https://github.com/devslab-kr/datalinq","last_synced_at":"2026-06-25T05:30:33.818Z","repository":{"id":364598591,"uuid":"1268139091","full_name":"devslab-kr/datalinq","owner":"devslab-kr","description":"Convention-driven cross-vendor JDBC data-migration TUI (TamboUI) — drop a folder, get a menu. 폴더만 떨구면 메뉴가 되는 데이터 마이그레이션 TUI.","archived":false,"fork":false,"pushed_at":"2026-06-13T16:34:39.000Z","size":201,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T18:24:42.018Z","etag":null,"topics":["cli","data-migration","database","etl","java","jdbc","tamboui","terminal-ui","tui"],"latest_commit_sha":null,"homepage":"https://devslab.kr","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devslab-kr.png","metadata":{"files":{"readme":"README.ko.md","changelog":"CHANGELOG.ko.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"custom":["https://devslab.kr"]}},"created_at":"2026-06-13T07:23:55.000Z","updated_at":"2026-06-13T16:34:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/devslab-kr/datalinq","commit_stats":null,"previous_names":["devslab-kr/datalinq"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/devslab-kr/datalinq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fdatalinq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fdatalinq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fdatalinq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fdatalinq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devslab-kr","download_url":"https://codeload.github.com/devslab-kr/datalinq/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devslab-kr%2Fdatalinq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34761844,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","data-migration","database","etl","java","jdbc","tamboui","terminal-ui","tui"],"created_at":"2026-06-25T05:30:33.036Z","updated_at":"2026-06-25T05:30:33.804Z","avatar_url":"https://github.com/devslab-kr.png","language":"Java","funding_links":["https://devslab.kr"],"categories":[],"sub_categories":[],"readme":"# DataLinq\n\n[English](README.md) | **한국어**\n\nTUI 기반 데이터 마이그레이션 도구입니다([TamboUI](https://github.com/tamboui/tamboui)로 제작).\n**기본 메뉴는 코드로 고정**되고, **마이그레이션 메뉴는 `sql/` 폴더를 스캔해 자동 발견**됩니다 —\n폴더 하나 떨구면 메뉴가 됩니다.\n\n**임의의 두 JDBC 데이터베이스 사이**로 행을 옮깁니다(크로스벤더, 소스 + 타겟). SQL Server,\nMariaDB/MySQL, PostgreSQL 드라이버는 **번들**되어 있고, 그 외(Oracle, H2, SQLite, ...)는 `driver`\n명령 한 줄로 내려받습니다. 연결은 **DB 종류 + host/port/database**(또는 raw URL)로 설정하며,\n`application.yml` 에 적거나 DB 연결 화면에서 바로 편집·저장할 수 있습니다.\n\n\u003e **DevsLab Co., Ltd.** (주식회사 데브스랩) 제작 · https://devslab.kr · Apache-2.0\n\n## 빠른 시작\n\n아무것도 없는 상태에서 첫 마이그레이션까지 다섯 단계. 모든 블록은 복붙하면 됩니다.\n\n### 1. 설치\n\n```bash\njbang app install datalinq@devslab-kr/datalinq   # `datalinq` 명령 생성 (jbang이 JDK도 준비해 줌)\n```\n\n\u003e jbang이 없나요? [최신 릴리스](https://github.com/devslab-kr/datalinq/releases/latest)에서 `datalinq.jar` 를 받아, 이 가이드의 `datalinq` 자리에 `java -jar datalinq.jar` 를 쓰면 됩니다. **JDK 21+** 필요.\n\n### 2. 둘러보기 — DB 없이도 됨\n\n```bash\ndatalinq init     # application.example.yml, i18n/, branding/, 샘플 sql/ 폴더를 여기에 생성\ndatalinq list     # sql/ 아래에서 발견된 마이그레이션 목록\ndatalinq          # TUI 실행 (방향키 / 숫자키로 이동, q 또는 Esc로 종료)\n```\n\n### 3. 데이터베이스 연결\n\n`init` 이 `application.example.yml` 을 만들어 줍니다. 복사해서 소스 하나, 타겟 하나를 채우세요:\n\n```bash\ncp application.example.yml application.yml\n```\n\n```yaml\ndatasources:\n  my-source:\n    type: sqlserver          # sqlserver | mariadb | postgresql   (또는: type: custom + raw url:)\n    host: localhost\n    port: 1433\n    database: SourceDb\n    username: sa\n    password: \"secret\"\n  my-target:\n    type: postgresql\n    host: localhost\n    port: 5432\n    database: TargetDb\n    username: postgres\n    password: \"secret\"\ndefaults:\n  source: my-source\n  target: my-target\n```\n\n```bash\ndatalinq config   # 해석된 설정 확인 (비밀번호는 마스킹)\n```\n\n\u003e 번들되지 않은 드라이버(Oracle, H2, SQLite, ...)가 필요하면 `datalinq driver oracle` 후 그 데이터소스를 `type: custom` + JDBC `url:` 로 설정하세요.\n\n### 4. 첫 마이그레이션 작성\n\n마이그레이션은 `sql/` 아래 폴더 하나입니다. 가장 단순한 **ETL** 은 소스 쿼리의 행을 타겟 테이블로 복사합니다. SELECT의 **컬럼 별칭이 곧 타겟 컬럼**이 되므로 INSERT문을 직접 쓰지 않습니다:\n\n```bash\nmkdir -p sql/01_Customers\n```\n\n`sql/01_Customers/source.sql`:\n\n```sql\nSELECT customer_id AS id,\n       full_name   AS name,\n       created_at  AS created\nFROM   customers\n```\n\n`sql/01_Customers/operation.properties`:\n\n```properties\ntype=etl\ntable=customers       # INSERT 대상 타겟 테이블\n```\n\n### 5. 실행 — 항상 dry-run 먼저\n\n```bash\ndatalinq run 0             # DRY-RUN: 소스를 읽지만 아무것도 쓰지 않음 (타겟 트랜잭션 롤백)\ndatalinq run 0 --execute   # 실제 실행: 단일 트랜잭션, 성공 시 커밋 / 오류 시 롤백\n```\n\n또는 TUI에서: `datalinq` 실행 → 마이그레이션 선택 → Enter. `sql/` 아래에 `NN_이름` 폴더를 더 떨구면 각각 메뉴 항목이 됩니다.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e👉 데이터가 실제로 옮겨지는 걸 보고 싶다면? Docker로 완전 복붙 데모 — 내 DB가 없어도 됩니다.\u003c/b\u003e\u003c/summary\u003e\n\nthrowaway PostgreSQL 두 개를 띄우고, 소스를 채우고, 마이그레이션을 실행해 복사된 행을 출력합니다. (end-to-end 검증 완료)\n\n```bash\n# 1. throwaway Postgres 두 개 (소스 5433, 타겟 5434)\ndocker run -d --name dl-src -p 5433:5432 -e POSTGRES_PASSWORD=demo postgres:16-alpine\ndocker run -d --name dl-tgt -p 5434:5432 -e POSTGRES_PASSWORD=demo postgres:16-alpine\nsleep 5\n\n# 2. 소스 채우기 + 빈 타겟 테이블 생성\ndocker exec dl-src psql -U postgres -c \"CREATE TABLE customers(customer_id int, full_name text, created_at timestamp); INSERT INTO customers VALUES (1,'Alice',now()),(2,'Bob',now());\"\ndocker exec dl-tgt psql -U postgres -c \"CREATE TABLE customers(id int primary key, name text, created timestamp);\"\n\n# 3. 설정 + 마이그레이션 하나가 든 작업 폴더\nmkdir -p dl-demo/sql/01_Customers \u0026\u0026 cd dl-demo\ncat \u003e application.yml \u003c\u003c'YAML'\ndatasources:\n  my-source:\n    type: postgresql\n    host: localhost\n    port: 5433\n    database: postgres\n    username: postgres\n    password: demo\n  my-target:\n    type: postgresql\n    host: localhost\n    port: 5434\n    database: postgres\n    username: postgres\n    password: demo\ndefaults:\n  source: my-source\n  target: my-target\nYAML\ncat \u003e sql/01_Customers/source.sql \u003c\u003c'SQL'\nSELECT customer_id AS id, full_name AS name, created_at AS created FROM customers\nSQL\nprintf 'type=etl\\ntable=customers\\n' \u003e sql/01_Customers/operation.properties\n\n# 4. 실제 실행 후 타겟 확인\ndatalinq run 0 --execute\ndocker exec dl-tgt psql -U postgres -c \"SELECT * FROM customers ORDER BY id;\"   # -\u003e Alice, Bob\n\n# 5. 정리\ncd .. \u0026\u0026 docker rm -f dl-src dl-tgt\n```\n\n\u003c/details\u003e\n\n## 마이그레이션 추가 = 폴더 떨구기\n\n```\nsql/\n├── 01_Approval_Lines/       # ETL: source.sql(별칭 SELECT) -\u003e 타겟 테이블\n│   ├── source.sql\n│   └── operation.properties     -\u003e  type=etl, table=approval_lines\n├── 03_Reset_Base_Data/      # SCRIPT: .sql 을 타겟에 실행(초기화/삭제)\n│   ├── 01_reset.sql\n│   └── operation.properties     -\u003e  type=script, destructive=true\n└── 05_Orders_With_Items/    # HANDLER: 커스텀 코드(마스터/디테일 분리)\n    ├── source.sql\n    └── operation.properties     -\u003e  handler=orders\n```\n\n- 폴더 `NN_Some_Name` -\u003e 순서 `NN`, 메뉴 라벨 `Some Name`.\n- `operation.properties` 는 선택사항입니다. `source.sql` 이 있으면 기본 ETL, 없으면 SCRIPT.\n\n## 세 가지 작업 유형\n\n| 유형 | 폴더 구성 | 하는 일 | 코드? |\n|------|-----------|---------|-------|\n| **etl** | `source.sql` + `table=\u003ctable\u003e` | 소스를 읽어 타겟에 자동 INSERT. SELECT의 **컬럼 별칭 = 타겟 컬럼**이라 INSERT문을 따로 쓰지 않음. | 없음 |\n| **script** | `.sql` 하나 이상 | **타겟**에 대해 실행(초기화/삭제). | 없음 |\n| **handler** | `source.sql` + `handler=\u003cname\u003e` | `MigrationHandler`(변환 / 마스터-디테일 / 생성키 FK), ServiceLoader로 발견. | ~20줄 |\n\n### Handler (하나의 결과셋이 여러 테이블이 될 때)\n\n```java\npublic class OrdersMigration extends MigrationHandler {\n    public String name() { return \"orders\"; }     // handler=orders 로 참조\n    public void migrate() throws Exception {\n        var rows = query(\"source.sql\");            // 평면 결과셋\n        // order_no 로 묶어 마스터(orders) insert -\u003e 생성된 id 받아 디테일 insert\n        long orderId = insert(\"orders\", values(\"order_no\", ..., \"customer_id\", ...));\n        insert(\"order_items\", values(\"order_id\", orderId, \"product_id\", ..., \"qty\", ...));\n    }\n}\n```\n\nHandler는 문자열 리플렉션이 아니라 **ServiceLoader**로 해석됩니다\n(`META-INF/services/kr.devslab.datalinq.engine.MigrationHandler` 에 등록) — 그래서 GraalVM\n네이티브 이미지 가능성이 열려 있습니다.\n\n## 설정 (`application.yml`)\n\n여러 개의 **이름 있는 데이터소스** — 어느 것이든 소스나 타겟이 될 수 있습니다. 작업은 이름으로\n고르고, 없으면 `defaults` 로 폴백합니다:\n\n```yaml\ndatasources:\n  # 구조화 타입은 host/port/database 로 JDBC URL을 만듭니다:\n  legacy-erp:                      # 소스\n    type: sqlserver                # sqlserver | mariadb | postgresql\n    host: SRC_HOST\n    port: 1433\n    database: SRC_DB\n    username: sa\n    password: \"\"\n  new-core:                        # 타겟 (mariadb 드라이버는 MySQL 서버에도 접속)\n    type: mariadb\n    host: TGT_HOST\n    port: 3306\n    database: TGT_DB\n    username: root\n    password: \"\"\n  # custom 타입은 직접 쓴 URL을 그대로 사용(내려받은 드라이버에 사용):\n  # warehouse:\n  #   type: custom\n  #   url: jdbc:oracle:thin:@HOST:1521:ORCL\n  #   username: app\n  #   password: \"\"\ndefaults:\n  source: legacy-erp               # 작업이 source= 를 지정하지 않을 때 사용\n  target: new-core                 # 작업이 target= 을 지정하지 않을 때 사용\noptions:\n  batch-size: 1000\n  dry-run-default: true\n  language: en                     # en | ko  (빈 값 = 시스템 로캘)\n  max-parallel: 4                  # 동시 실행 작업 수(DB 연결 수를 제한)\n  mask-password: true              # DB 연결 화면: 비밀번호 마스킹(false = 평문 표시)\n  # sql-dir: /path/to/sql          # 외부 마이그레이션 폴더(빈 값 = ./sql)\n```\n\n작업은 operation.properties의 `source=\u003cname\u003e` / `target=\u003cname\u003e` 으로 실행마다 재정의할 수 있습니다.\n`application.example.yml` 을 `application.yml`(gitignore됨)로 복사하세요. 앱 안(DB 연결 화면)에서\n편집·저장할 수도 있습니다.\n\n## 데이터베이스 드라이버\n\n- **번들**(항상 동작): SQL Server, MariaDB / MySQL, PostgreSQL.\n- **다운로드** — Maven Central에서 `~/.datalinq/drivers/` 로 받아 다음 실행 시 로드:\n\n  ```bash\n  datalinq driver               # 받을 수 있는 목록\n  datalinq driver oracle        # 하나 받기 (oracle | mysql | h2 | sqlite | postgresql)\n  ```\n\n  해당 폴더에 JDBC 드라이버 `.jar` 을 손으로 떨궈도 됩니다. 외부에서 로드된 드라이버는\n  `DriverShim` 을 통해 등록됩니다(그렇지 않으면 JDK의 `DriverManager` 가 외부 클래스로더의\n  드라이버를 무시함). **custom** 타입 + 직접 쓴 URL 로 사용하세요.\n\n## 안전장치\n\n- **기본이 dry-run** — 타겟 트랜잭션을 롤백하므로 아무것도 쓰지 않습니다.\n- `destructive=true` 작업은 실행 전 명시적 확인을 요구합니다.\n- 각 작업은 **하나의 타겟 트랜잭션** 안에서 실행: 성공 시 커밋, 오류 시 롤백.\n\n## 설치 \u0026 실행\n\n기본 명령은 **TUI** 이고, 모든 하위 명령은 스크립트화할 수 있습니다.\n\n### jbang으로 (권장)\n\n[jbang](https://www.jbang.dev/) 은 DataLinq를 실제 `datalinq` 명령으로 설치해 주고, JDK가 없으면\n알맞은 JDK까지 받아 줍니다:\n\n```bash\njbang app install datalinq@devslab-kr/datalinq   # 한 번만 - `datalinq` 명령 생성\ndatalinq                                          # TUI 실행 (마이그레이션 / DB 연결 / 설정 / 정보)\ndatalinq init                                     # 편집용 기본값 생성 (i18n/, branding/, 예시 설정, sql/)\ndatalinq list                                     # 발견된 작업 목록\ndatalinq run 0                                    # 0번 작업 dry-run\ndatalinq run 0 --execute                          # 실제 쓰기\n```\n\n설치 없이 한 번만 실행하려면: `jbang datalinq@devslab-kr/datalinq`.\n\n### jar로 (jbang 없이)\n\n[최신 릴리스](https://github.com/devslab-kr/datalinq/releases/latest)에서 `datalinq.jar` 를 받아\n직접 실행합니다(**JDK 21+** 필요). `java -jar datalinq.jar \u003c명령\u003e` 은 `datalinq \u003c명령\u003e` 과 동일합니다:\n\n```bash\njava -jar datalinq.jar              # TUI\njava -jar datalinq.jar config       # 해석된 설정 표시(비밀번호 마스킹)\njava -jar datalinq.jar run 0 --execute\n```\n\n개발 중에는 `./gradlew run --args=\"...\"` 도 동작하며, `./gradlew shadowJar` 로 jar을 빌드합니다.\n\n## 상태\n\n엔진(스캐너 / ETL / script / handler / 트랜잭션 / dry-run), `application.yml` 설정,\n번들 + 다운로드 JDBC 드라이버, CLI, 그리고 **TamboUI TUI**(마이그레이션, DB 연결, 설정, 정보 —\n모두 동일한 `MigrationEngine` 호출)가 구현·검증되었습니다.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevslab-kr%2Fdatalinq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevslab-kr%2Fdatalinq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevslab-kr%2Fdatalinq/lists"}