{"id":50731344,"url":"https://github.com/mavstuff/transqlate","last_synced_at":"2026-06-10T09:01:12.877Z","repository":{"id":359115032,"uuid":"1244571099","full_name":"mavstuff/Transqlate","owner":"mavstuff","description":"Migrate a Microsoft SQL Server database to PostgreSQL easily","archived":false,"fork":false,"pushed_at":"2026-05-20T13:47:32.000Z","size":846,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-20T16:53:16.139Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mavstuff.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-20T11:46:41.000Z","updated_at":"2026-05-20T13:47:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mavstuff/Transqlate","commit_stats":null,"previous_names":["mavstuff/transqlate"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/mavstuff/Transqlate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mavstuff%2FTransqlate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mavstuff%2FTransqlate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mavstuff%2FTransqlate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mavstuff%2FTransqlate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mavstuff","download_url":"https://codeload.github.com/mavstuff/Transqlate/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mavstuff%2FTransqlate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34144680,"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-10T02:00:07.152Z","response_time":89,"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":[],"created_at":"2026-06-10T09:01:11.820Z","updated_at":"2026-06-10T09:01:12.871Z","avatar_url":"https://github.com/mavstuff.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/logo.png\" alt=\"Transqlate logo\" width=\"180\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eTransqlate\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/actions/workflows/ci.yml\"\u003e\n    \u003cimg src=\"https://github.com/mavstuff/Transqlate/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/mavstuff/Transqlate\"\u003e\n    \u003cimg src=\"https://codecov.io/gh/mavstuff/Transqlate/graph/badge.svg\" alt=\"Codecov\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.python.org/\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Python-3.10+-3776AB?logo=python\u0026logoColor=white\" alt=\"Python 3.10+\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"LICENSE.txt\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/license/mavstuff/Transqlate?style=flat-square\" alt=\"License: MIT\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/security/dependabot\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/dependabot-enabled-025e8c?logo=dependabot\u0026logoColor=white\" alt=\"Dependabot\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/releases\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/v/release/mavstuff/Transqlate?style=flat-square\" alt=\"Release\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/commits/master\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/last-commit/mavstuff/Transqlate?style=flat-square\" alt=\"Last commit\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/issues\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/issues/mavstuff/Transqlate?style=flat-square\" alt=\"Open issues\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/pulls\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/issues-pr/mavstuff/Transqlate?style=flat-square\" alt=\"Open pull requests\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/graphs/contributors\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/contributors/mavstuff/Transqlate?style=flat-square\" alt=\"Contributors\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/stargazers\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/stars/mavstuff/Transqlate?style=flat-square\" alt=\"Stars\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/network/members\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/forks/mavstuff/Transqlate?style=flat-square\" alt=\"Forks\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/SQL%20Server-%E2%86%92%20PostgreSQL-336791?logo=postgresql\u0026logoColor=white\" alt=\"SQL Server to PostgreSQL\"\u003e\n  \u003ca href=\"https://pypi.org/project/transqlate/\"\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/v/transqlate?style=flat-square\u0026logo=pypi\" alt=\"PyPI version\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://pypi.org/project/transqlate/\"\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/dm/transqlate?style=flat-square\u0026logo=pypi\" alt=\"PyPI downloads\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/mavstuff/Transqlate/issues?q=label%3Asecurity+is%3Aopen\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/issues-search/mavstuff/Transqlate?query=label%3Asecurity+is%3Aopen\u0026style=flat-square\u0026label=security%20issues\" alt=\"Security issues\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Migrate a Microsoft SQL Server database to PostgreSQL.\n\u003c/p\u003e\n\n---\n\nMigrate in two steps:\n\n1. **Export** — dump data to a portable, human-readable UTF-8 TSV package (folder or `.zip`), with schema metadata discovered from the live MSSQL database.\n2. **Import** — recreate tables, foreign keys, and indexes in PostgreSQL, then load the TSV files.\n\nThe main entry point is `tq.py`. The implementation lives in the `transqlate` package.\n\n**Schema is inferred from the source database**\n\n## Requirements\n\n- Python 3.10+\n- SQL Server reachable from the export machine (`pymssql`)\n- PostgreSQL reachable from the import machine (`psycopg2-binary`)\n\n```bash\npip install -r requirements.txt\n```\n\n## Quick start\n\n### 1. Export on Windows (SQL Server host)\n\nPass connection settings on the command line:\n\n```bash\npython tq.py export --output myapp-export.zip ^\n  --mssql-server localhost --mssql-port 1433 ^\n  --mssql-database MyApp --mssql-user sa --mssql-password secret\n```\n\nOr set environment variables and omit the flags (cmd.exe):\n\n```bat\nset MSSQL_SERVER=localhost\nset MSSQL_PORT=1433\nset MSSQL_DATABASE=MyApp\nset MSSQL_USER=sa\nset MSSQL_PASSWORD=secret\n\npython tq.py export --output myapp-export.zip\n```\n\n### 2. Transfer the dump\n\nCopy `myapp-export.zip` (or the export folder) to the PostgreSQL host.\n\n### 3. Import on Linux (or any host with network access to PostgreSQL)\n\nPass connection settings on the command line:\n\n```bash\npython tq.py import --input myapp-export.zip --drop-existing \\\n  --postgres-host localhost --postgres-port 5432 \\\n  --postgres-database myapp --postgres-user myapp --postgres-password secret\n```\n\nOr set environment variables and omit the flags:\n\n```bash\nexport POSTGRES_HOST=localhost\nexport POSTGRES_PORT=5432\nexport POSTGRES_DATABASE=myapp\nexport POSTGRES_USER=myapp\nexport POSTGRES_PASSWORD=secret\n\npython tq.py import --input myapp-export.zip --drop-existing\n```\n\n`--drop-existing` drops and recreates tables (destructive). Omit it when loading into empty tables created manually.\n\nIf `--mssql-password` or `--postgres-password` is omitted (and not set via `MSSQL_PASSWORD` / `POSTGRES_PASSWORD`), you are prompted interactively; input is not echoed.\n\n## Dump layout\n\n```\nmyapp-export/\n  manifest.json      # format version, import order, row counts\n  schema.json        # tables, columns, PKs, FKs, indexes (from MSSQL)\n  dbo/\n    Customers.tsv\n    Orders.tsv\n    ...\n```\n\n- **Encoding:** UTF-8  \n- **Delimiter:** tab (TSV)  \n- **NULL:** empty field  \n- **Booleans:** `true` / `false`  \n- **Dates/times:** ISO-like `YYYY-MM-DD HH:MM:SS`  \n- **Binary:** hex string (restored on import)\n\n## Commands\n\n### `tq export`\n\nDiscover schema and export all base tables in the given MSSQL schema(s).\n\n```bash\npython tq.py export --help\n```\n\n| Option | Env var | Default | Description |\n|--------|---------|---------|-------------|\n| `--output` | | `transqlate-export` | Folder or `.zip` path |\n| `--mssql-server` | `MSSQL_SERVER` | `localhost` | Server host |\n| `--mssql-port` | `MSSQL_PORT` | `1433` | TCP port |\n| `--mssql-database` | `MSSQL_DATABASE` | | Database name (required) |\n| `--mssql-user` | `MSSQL_USER` | | Login (required) |\n| `--mssql-password` | `MSSQL_PASSWORD` | | Password (prompted if omitted) |\n| `--mssql-schemas` | `MSSQL_SCHEMAS` | `dbo` | Comma-separated schemas |\n| `--exclude-tables` | | | `schema.table` to skip |\n| `--batch-size` | | `2000` | Rows per fetch |\n\n**Examples (command line)**\n\n```bash\n# Export dbo only to a directory\npython tq.py export --output ./dump \\\n  --mssql-server localhost --mssql-database Sales \\\n  --mssql-user sa --mssql-password secret\n\n# Export dbo + custom schema to zip\npython tq.py export --output sales.zip --mssql-schemas dbo,audit \\\n  --mssql-server localhost --mssql-database Sales \\\n  --mssql-user sa --mssql-password secret\n\n# Skip a table\npython tq.py export --exclude-tables dbo.__MigrationHistory \\\n  --mssql-database Sales --mssql-user sa --mssql-password secret\n\n# Remote SQL Server\npython tq.py export --mssql-server db.example.com --mssql-port 1433 \\\n  --mssql-database MyApp --mssql-user migrator --mssql-password \"%PASS%\" \\\n  --output myapp-export.zip\n```\n\n**Examples (environment variables)**\n\n```bash\nexport MSSQL_SERVER=db.example.com\nexport MSSQL_PORT=1433\nexport MSSQL_DATABASE=MyApp\nexport MSSQL_USER=migrator\nexport MSSQL_PASSWORD=secret\nexport MSSQL_SCHEMAS=dbo,audit\n\npython tq.py export --output sales.zip --exclude-tables dbo.__MigrationHistory\n```\n\n### `tq import`\n\nApply DDL (unless disabled), truncate (unless disabled), load data in FK-safe order, reset identity sequences.\n\n```bash\npython tq.py import --help\n```\n\n| Option | Env var | Default | Description |\n|--------|---------|---------|-------------|\n| `--input` | | `transqlate-export` | Dump folder or `.zip` |\n| `--postgres-host` | `POSTGRES_HOST` | `localhost` | |\n| `--postgres-port` | `POSTGRES_PORT` | `5432` | |\n| `--postgres-database` | `POSTGRES_DATABASE` | | Target DB (required) |\n| `--postgres-user` | `POSTGRES_USER` | | (required) |\n| `--postgres-password` | `POSTGRES_PASSWORD` | | Password (prompted if omitted) |\n| `--postgres-schema` | `POSTGRES_SCHEMA` | | Force all tables into this PG schema |\n| `--batch-size` | | `500` | INSERT batch size |\n| `--no-truncate` | | | Keep existing rows (may duplicate PKs) |\n| `--no-create-schema` | | | Tables must already exist |\n| `--drop-existing` | | | `DROP TABLE ... CASCADE` before create |\n| `--skip-indexes` | | | Skip non-PK indexes |\n| `--skip-foreign-keys` | | | Skip FK constraints |\n| `--schema-only` | | | DDL only, no data |\n\n**Examples (command line)**\n\n```bash\n# Full migration into a new database\ncreatedb myapp\npython tq.py import --input myapp-export.zip --drop-existing \\\n  --postgres-host localhost --postgres-database myapp \\\n  --postgres-user myapp --postgres-password secret\n\n# Data only (schema created manually)\npsql -d myapp -f schema.sql\npython tq.py import --input myapp-export.zip --no-create-schema \\\n  --postgres-database myapp --postgres-user myapp --postgres-password secret\n\n# Map everything into a single PG schema\npython tq.py import --input dump.zip --postgres-schema app --drop-existing \\\n  --postgres-database myapp --postgres-user myapp --postgres-password secret\n\n# Staging: tables + data, defer FKs for speed\npython tq.py import --input dump.zip --skip-foreign-keys --drop-existing \\\n  --postgres-database myapp --postgres-user myapp --postgres-password secret\n```\n\n**Examples (environment variables)**\n\n```bash\nexport POSTGRES_HOST=localhost\nexport POSTGRES_DATABASE=myapp\nexport POSTGRES_USER=myapp\nexport POSTGRES_PASSWORD=secret\n\ncreatedb myapp\npython tq.py import --input myapp-export.zip --drop-existing\n\n# Schema already applied manually\npython tq.py import --input myapp-export.zip --no-create-schema\n\n# Reload: DDL only, then data\npython tq.py import --input myapp-export.zip --drop-existing --schema-only\npython tq.py import --input myapp-export.zip --no-create-schema\n```\n\n### `tq schema`\n\nEmit PostgreSQL DDL from `schema.json` inside a dump (no database connection).\n\n```bash\npython tq.py schema --input myapp-export.zip --output postgresql-schema.sql\npython tq.py schema --input ./dump --output - | less\n```\n\nUseful for review, manual edits, or DBA sign-off before import.\n\n## Schema mapping\n\n| SQL Server | PostgreSQL (default) |\n|------------|----------------------|\n| `dbo` schema | `public` |\n| Other schemas | Same name (created if missing) |\n| `bit` | `boolean` |\n| `tinyint` | `smallint` |\n| `int` / `bigint` | `integer` / `bigint` |\n| `nvarchar(n)` / `varchar` | `varchar(n)` |\n| `nvarchar(max)` / `text` | `text` |\n| `datetime` / `datetime2` | `timestamp` |\n| `uniqueidentifier` | `uuid` |\n| `decimal(p,s)` | `numeric(p,s)` |\n| Identity columns | `GENERATED BY DEFAULT AS IDENTITY` |\n\nIdentifiers keep their original names (quoted in PostgreSQL when needed). Foreign keys and non-clustered indexes are recreated from MSSQL metadata. Computed columns are omitted from export.\n\n## Import order\n\nTables are ordered topologically using foreign-key dependencies so parent rows load before children. Cycles fall back to alphabetical order; use `--skip-foreign-keys` if you need to load in a custom order and add constraints later.\n\n## Typical workflows\n\n### Greenfield database\n\n```bash\npython tq.py export --output prod.zip \\\n  --mssql-server localhost --mssql-database Prod \\\n  --mssql-user sa --mssql-password secret\n\n# on PG server\ncreatedb prod_pg\npython tq.py import --input prod.zip --drop-existing \\\n  --postgres-host localhost --postgres-database prod_pg \\\n  --postgres-user prod_pg --postgres-password secret\n```\n\nWith env vars set (`MSSQL_*` / `POSTGRES_*`), the same flow is:\n\n```bash\npython tq.py export --output prod.zip\ncreatedb prod_pg\npython tq.py import --input prod.zip --drop-existing\n```\n\n### Review DDL before load\n\n```bash\npython tq.py export --output prod.zip ...\npython tq.py schema --input prod.zip --output review.sql\n# edit review.sql if needed, then:\npsql -d prod_pg -f review.sql\npython tq.py import --input prod.zip --no-create-schema\n```\n\n### Partial export (one schema, exclude staging tables)\n\n```bash\npython tq.py export --mssql-schemas dbo \\\n  --exclude-tables dbo.StagingOrders,dbo.TempImport \\\n  --output partial.zip\n```\n\n### Re-run data after failed import\n\n```bash\npython tq.py import --input prod.zip --drop-existing\n# or truncate-only:\npython tq.py import --input prod.zip --no-create-schema\n```\n\n## Troubleshooting\n\n| Problem | Things to try |\n|---------|----------------|\n| `Missing pymssql` | `pip install pymssql` on export host |\n| `Missing psycopg2` | `pip install psycopg2-binary` on import host |\n| Column mismatch on import | Re-export; dump and code version must match |\n| FK violation on import | Ensure full dump; try `--skip-foreign-keys` then add FKs |\n| Duplicate key | Use default truncate or `--drop-existing` |\n| Permission denied on PG | Grant `CREATE` on database and schemas |\n| Unicode garbled | Confirm TSV opened as UTF-8; export always writes UTF-8 |\n\n## Project layout\n\n```\ntq.py                      # CLI entry\ntransqlate/\n  cli.py                   # argparse subcommands\n  dump_format.py           # TSV + manifest + schema.json\n  types_map.py             # MSSQL → PG type mapping\n  archive.py               # zip helpers\n  mssql/\n    connection.py\n    schema.py              # discover metadata\n    export.py\n  postgres/\n    connection.py\n    schema.py              # DDL generation\n    import_data.py\n```\n\n(c) 2026 Artem Moroz \u003cartem.moroz@gmail.com\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmavstuff%2Ftransqlate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmavstuff%2Ftransqlate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmavstuff%2Ftransqlate/lists"}