typeorm icon indicating copy to clipboard operation
typeorm copied to clipboard

Output migration SQL

Open alper opened this issue 1 year ago • 1 comments

Feature Description

I was kinda disappointed to see that migration:show does not output the SQL of the migration.

This is a relatively essential feature for many applications for instance if you want to lint the SQL through a tool like: https://github.com/sbdchd/squawk

The Solution

I would like a command similar to django-admin sqlmigrate where you can pass a migration by name (as output by migration:show) and then get the SQL output to stdout.

It would be nice if it were possible to do this without a connection to the database. I think it's possible here because all the SQL is already materialized whereas in Django it isn't.

Considered Alternatives

I checked but could not find any workaround other than patching the migration files (or what we are doing is to rip the SQL code from the migration files using the typescript APIs).

Additional Context

No response

Relevant Database Driver(s)

  • [ ] aurora-mysql
  • [ ] aurora-postgres
  • [ ] better-sqlite3
  • [ ] cockroachdb
  • [ ] cordova
  • [ ] expo
  • [ ] mongodb
  • [ ] mysql
  • [ ] nativescript
  • [ ] oracle
  • [ ] postgres
  • [ ] react-native
  • [ ] sap
  • [ ] spanner
  • [ ] sqlite
  • [ ] sqlite-abstract
  • [ ] sqljs
  • [ ] sqlserver

Are you willing to resolve this issue by submitting a Pull Request?

Yes, I have the time, and I know how to start.

alper avatar Jul 17 '24 14:07 alper

Just wanted to share how I ended up doing because of this limitation: basically I run the migrations on a docker container, extract the SQL statements executed and then run squawk on the sql file. Here's an example using github actions:

  lint-db-migrations:
    name: Lint DB Migrations
    permissions:
      contents: read
      pull-requests: write
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgrespassword
          POSTGRES_DB: mydb
        ports:
          - 54320:5432
        # Set health checks to wait until postgres has started
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    runs-on: ubuntu-latest
    steps:
      - name: checkout target branch
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.ref }}

      - name: Run migrations from the target branch
        run: yarn typeorm migration:run

      - name: Enable logs for the postgres user
        run: PGPASSWORD=postgrespassword psql -h localhost -p 54320 -U postgres -d mydb -c "ALTER ROLE postgres SET log_statement TO 'all';"

      - name: checkout source branch
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.ref }}

 
      - name: Run migrations from the source branch
        run: |
          yarn typeorm migration:run

      - name: Get the docker logs from the postgres container and save it into a file
        run: docker logs "${{ job.services.postgres.id }}" >&  logs

      - name: Extract the queries from the logs
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: node scripts/gh-actions/extract_sql_statements.js

      - name: View the queries
        run: cat logs

      - name: View the extracted queries
        run: cat extracted_statements.sql

      - uses: sbdchd/squawk-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          files: 'extracted_statements.sql'
          fail-on-violations: true

and the js script to extract the logs:

const fs = require('fs')
const readline = require('readline')

const inputFilePath = 'logs'
const outputFilePath = 'extracted_statements.sql'

async function extractSQLStatements() {
  const fileStream = fs.createReadStream(inputFilePath)
  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity,
  })

  const sqlStatements = []
  // extract only logs from lines that start with "2024-09-16 08:40:30.307 UTC [348] LOG:  statement:"
  const statementRegex =
    /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC \[\d+\] LOG:  statement: (.*)$/
  const logRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC \[\d+\](.*)$/

  for await (const line of rl) {
    const match = statementRegex.exec(line)
    if (match) {
      let statement = match[1].trim()
      if (!statement) {
        // Handle multi-line SQL statements
        statement = ''
        let nextLine = (await rl[Symbol.asyncIterator]().next()).value
        while (nextLine && !logRegex.exec(nextLine)) {
          statement += nextLine + '\n'
          nextLine = (await rl[Symbol.asyncIterator]().next()).value
        }
      }
      if (!statement.endsWith(';')) {
        statement += ';'
      }
      sqlStatements.push(statement.trim())
    }
  }

  fs.writeFileSync(outputFilePath, sqlStatements.join('\n\n'))
  console.log(`Extracted SQL statements have been written to ${outputFilePath}`)
}

extractSQLStatements().catch((err) => console.error(err))

bmbferreira avatar Oct 17 '24 15:10 bmbferreira

Hey everyone! Had this issue as well, so I created a cli script that allows to generate .sql files from typeorm migration files, and revert them as typeorm migration:revert does it.

https://www.npmjs.com/package/typeorm-raw-migrations

vovados1 avatar May 01 '25 08:05 vovados1