要件
次の特性を持つデータベース移行ツールが必要です:
- すべての移行は単一の SQL ファイルに書かれており、これは「アップ」部分と「ダウン」部分の両方を意味します。これにより、Copilot がロールバック移行を実行できるようになります。また、これが裸の SQL であるという事実により、最も柔軟でサポートされているソリューションになります。
- 現在適用されているバージョンはツールで管理する必要があります。ツールを自立させたいです。
- このツールで Postgres、MySQL、SQL Server などのさまざまなデータベースをサポートしたいので、その意味で拡張可能である必要があります。
- サイズが大きくなりすぎないようにするため、必要なデータベースのドライバーのみを、理想的にはオンデマンドでインストールする必要があります。
- 私が取り組んでいるほとんどのプロジェクトは JavaScript エコシステムの一部であるため、これを JavaScript エコシステムの一部にしたいと考えています。
- すべての移行はトランザクション内で実行する必要があります。
導入
これらのポイントの多くは、tern と呼ばれるこの素晴らしいツールを使った私の経験から生まれました。 JavaScript には同じ機能がないのが残念でした。 (あるいは、私がグーグルするのが苦手なのかもしれません...)。そこで、これは自分自身にとっては素晴らしいコーディングの練習になるし、他の人にとっては興味深いストーリーになるかもしれないと判断しました:)
発達
パート 1. ツールの設計
CLI ツールの設計を盗みましょう!
- すべての移行には、
_ .sql という名前付けスキームが適用されます。ここで、番号は移行バージョン番号を表します (例: 001_initial_setup.sql)。 - すべての移行は単一のディレクトリに存在します。
- データベース ドライバーは、事前にバンドルされたパッケージか、ある種の npm install
を発行するだけで、オンデマンドでダウンロードされます。
ツールの構文は次のようになります。 martlet up --database-url
「上」はまだ適用されていないすべての移行を適用する必要があり、「下」は指定されたバージョンにロールバックする必要があります。
オプションの意味とデフォルトは次のとおりです:
- database-url - データベースの接続文字列。デフォルトでは環境変数 DATABASE_URL を検索します。
- driver - 使用するデータベースドライバー。最初のバージョンでは、「pg」という名前のオプションを使用して Postgres のみをサポートします。
- dir - マイグレーションが存在するディレクトリ、デフォルトはマイグレーションです
ご覧のとおり、実際のコードを記述する前に、ツールを呼び出す方法を考えることから始めました。これは良い習慣であり、要件を実現し、開発サイクルを短縮するのに役立ちます。
パート 2. 実装
2.1 解析オプション
それでは、まず最初に! Index.js ファイルを作成し、ヘルプメッセージを出力してみましょう。次のようになります:
function printHelp() { console.log( "Usage: martlet up --driver <driver> --dir <dir> --database-url <url>", ); console.log( " martlet down <version> --driver <driver> --dir <dir> --database-url <url>", ); console.log( " <version> is a number that specifies the version to migrate down to", ); console.log("Options:"); console.log(' --driver <driver> Driver to use, default is "pg"'); console.log(' --dir <dir> Directory to use, default is "migrations"'); console.log( " --database-url <url> Database URL to use, default is DATABASE_URL environment variable", ); } printHelp(); </url> </dir></driver></version></url> </dir></driver></version></url> </dir></driver>
次にオプションを解析します:
export function parseOptions(args) { const options = { dir: "migrations", driver: "pg", databaseUrl: process.env.DATABASE_URL, }; for (let idx = 0; idx <p>ご覧のとおり、私は解析にライブラリを使用していません。単に引数リストを反復処理して、すべてのオプションを処理するだけです。したがって、ブール値オプションがある場合は反復インデックスを 1 シフトし、値を持つオプションがある場合は 2 シフトします。</p> <h4> 2.2 ドライバーアダプターの実装 </h4> <p>複数のドライバーをサポートするには、データベースにアクセスするためのユニバーサル インターフェイスが必要です。以下にその様子を示します:<br> </p> <pre class="brush:php;toolbar:false">interface Adapter { connect(url: string): Promise<void>; transact(query: (fn: (text) => Promise<resultset>)): Promise<resultset>; close(): Promise<void>; } </void></resultset></resultset></void>
connect と close は非常に明白な関数だと思います。transact メソッドについて説明しましょう。クエリ テキストを受け取り、中間結果を含む Promise を返す関数とともに呼び出される関数を受け入れる必要があります。この複雑さは、トランザクション内で複数のクエリを実行する機能を提供する一般的なインターフェイスを備えている必要があります。使用例を見ると理解しやすいです。
アダプターが postgres ドライバーを探す方法は次のとおりです。
class PGAdapter { constructor(driver) { this.driver = driver; } async connect(url) { this.sql = this.driver(url); } async transact(query) { return this.sql.begin((sql) => ( query((text) => sql.unsafe(text)) )); } async close() { await this.sql.end(); } }
使用例は次のようになります:
import postgres from "postgres"; const adapter = new PGAdapter(postgres); await adapter.connect(url); await adapter.transact(async (sql) => { const rows = await sql("SELECT * FROM table1"); await sql(`INSERT INTO table2 (id) VALUES (${rows[0].id})`); });
2.3 オンデマンドドライバーのインストール
const PACKAGES = { pg: "postgres@3.4.4", }; const downloadDriver = async (driver) => { const pkg = PACKAGES[driver]; if (!pkg) { throw new Error(`Unknown driver: ${driver}`); } try { await stat(join(process.cwd(), "yarn.lock")); const lockfile = await readFile(join(process.cwd(), "yarn.lock")); const packagejson = await readFile(join(process.cwd(), "package.json")); spawnSync("yarn", ["add", pkg], { stdio: "inherit", }); await writeFile(join(process.cwd(), "yarn.lock"), lockfile); await writeFile(join(process.cwd(), "package.json"), packagejson); return; } catch {} spawnSync("npm", ["install", "--no-save", "--legacy-peer-deps", pkg], { stdio: "inherit", }); };
最初はyarnを使用してドライバーをインストールしようとしますが、ディレクトリ内に差分を生成したくないので、yarn.lockファイルとpackage.jsonファイルを保存します。糸が利用できない場合は、npm に戻ります。
ドライバーがインストールされていることを確認したら、アダプターを作成して使用できます。
export async function loadAdapter(driver) { await downloadDriver(driver); return import(PACKAGES[driver].split("@")[0]).then( (m) => new PGAdapter(m.default), );
2.4 移行ロジックの実装
まず、データベースに接続して現在のバージョンを取得します。
await adapter.connect(options.databaseUrl); console.log("Connected to database"); const currentVersion = await adapter.transact(async (sql) => { await sql(`create table if not exists schema_migrations ( version integer primary key )`); const result = await sql(`select version from schema_migrations limit 1`); return result[0]?.version || 0; }); console.log(`Current version: ${currentVersion}`);
Then, we read the migrations directory and sort them by version. After that, we apply every migration that has a version greater than the current one. I will just present the actual migration in the following snippet:
await adapter.transact(async (sql) => { await sql(upMigration); await sql( `insert into schema_migrations (version) values (${version})` ); await sql(`delete from schema_migrations where version != ${version}`); });
The rollback migration is similar, but we sort the migrations in reverse order and apply them until we reach the desired version.
3. Testing
I decided not to use any specific testing framework but use the built-in nodejs testing capabilities. They include the test runner and the assertion package.
import { it, before, after, describe } from "node:test"; import assert from "node:assert";
And to execute tests I would run node --test --test-concurrency=1.
Actually, I was writing the code in a sort of TDD manner. I didn't validate that my migrations code worked by hand, but I was writing it along with tests. That's why I decided that end-to-end tests would be the best fit for this tool.
For such an approach, tests would need to bootstrap an empty database, apply some migrations, check that database contents are correct, and then roll back to the initial state and validate that the database is empty.
To run a database, I used the "testcontainers" library, which provides a nice wrapper around docker.
before(async () => { console.log("Starting container"); container = await new GenericContainer("postgres:16-alpine") .withExposedPorts(5432) .withEnvironment({ POSTGRES_PASSWORD: "password" }) .start(); }); after(async () => { await container.stop(); });
I wrote some simple migrations and tested that they worked as expected. Here is an example of a database state validation:
const sql = pg(`postgres://postgres:password@localhost:${port}/postgres`); const result = await sql`select * from schema_migrations`; assert.deepEqual(result, [{ version: 2 }]); const tables = await sql`select table_name from information_schema.tables where table_schema = 'public'`; assert.deepEqual(tables, [ { table_name: "schema_migrations" }, { table_name: "test" }, ]);
4. Conclusion
This was an example of how I would approach the development of a simple CLI tool in the javascript ecosystem. I want to note that the modern javascript ecosystem is pretty charged and powerful, and I managed to implement the tool with a minimum of external dependencies. I used a postgres driver that would be downloaded on demand and testcontainers for tests. I think that approach gives developers the most flexibility and control over the application.
5. References
- martlet repo
- tern
- postgres driver
以上がコーディング演習: Nodejs のデータベース移行ツールの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

PythonとJavaScriptの主な違いは、タイプシステムとアプリケーションシナリオです。 1。Pythonは、科学的コンピューティングとデータ分析に適した動的タイプを使用します。 2。JavaScriptは弱いタイプを採用し、フロントエンドとフルスタックの開発で広く使用されています。この2つは、非同期プログラミングとパフォーマンスの最適化に独自の利点があり、選択する際にプロジェクトの要件に従って決定する必要があります。

PythonまたはJavaScriptを選択するかどうかは、プロジェクトの種類によって異なります。1)データサイエンスおよび自動化タスクのPythonを選択します。 2)フロントエンドとフルスタック開発のためにJavaScriptを選択します。 Pythonは、データ処理と自動化における強力なライブラリに好まれていますが、JavaScriptはWebインタラクションとフルスタック開発の利点に不可欠です。

PythonとJavaScriptにはそれぞれ独自の利点があり、選択はプロジェクトのニーズと個人的な好みに依存します。 1. Pythonは、データサイエンスやバックエンド開発に適した簡潔な構文を備えた学習が簡単ですが、実行速度が遅くなっています。 2。JavaScriptはフロントエンド開発のいたるところにあり、強力な非同期プログラミング機能を備えています。 node.jsはフルスタックの開発に適していますが、構文は複雑でエラーが発生しやすい場合があります。

javascriptisnotbuiltoncorc;それは、解釈されていることを解釈しました。

JavaScriptは、フロントエンドおよびバックエンド開発に使用できます。フロントエンドは、DOM操作を介してユーザーエクスペリエンスを強化し、バックエンドはnode.jsを介してサーバータスクを処理することを処理します。 1.フロントエンドの例:Webページテキストのコンテンツを変更します。 2。バックエンドの例:node.jsサーバーを作成します。

PythonまたはJavaScriptの選択は、キャリア開発、学習曲線、エコシステムに基づいている必要があります。1)キャリア開発:Pythonはデータサイエンスとバックエンド開発に適していますが、JavaScriptはフロントエンドおよびフルスタック開発に適しています。 2)学習曲線:Python構文は簡潔で初心者に適しています。 JavaScriptの構文は柔軟です。 3)エコシステム:Pythonには豊富な科学コンピューティングライブラリがあり、JavaScriptには強力なフロントエンドフレームワークがあります。

JavaScriptフレームワークのパワーは、開発を簡素化し、ユーザーエクスペリエンスとアプリケーションのパフォーマンスを向上させることにあります。フレームワークを選択するときは、次のことを検討してください。1。プロジェクトのサイズと複雑さ、2。チームエクスペリエンス、3。エコシステムとコミュニティサポート。

はじめに私はあなたがそれを奇妙に思うかもしれないことを知っています、JavaScript、C、およびブラウザは正確に何をしなければなりませんか?彼らは無関係であるように見えますが、実際、彼らは現代のウェブ開発において非常に重要な役割を果たしています。今日は、これら3つの間の密接なつながりについて説明します。この記事を通して、JavaScriptがブラウザでどのように実行されるか、ブラウザエンジンでのCの役割、およびそれらが協力してWebページのレンダリングと相互作用を駆動する方法を学びます。私たちは皆、JavaScriptとブラウザの関係を知っています。 JavaScriptは、フロントエンド開発のコア言語です。ブラウザで直接実行され、Webページが鮮明で興味深いものになります。なぜJavascrを疑問に思ったことがありますか


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

VSCode Windows 64 ビットのダウンロード
Microsoft によって発売された無料で強力な IDE エディター

SublimeText3 中国語版
中国語版、とても使いやすい

mPDF
mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

Dreamweaver Mac版
ビジュアル Web 開発ツール
