Closed8

prismaを使って既存のテーブルからscalikejdbc用のmapperクラスを生成する

ara_ta3ara_ta3

prismaのversion確認用のpackage.json

{
  "name": "prisma-generate-scala",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@prisma/generator-helper": "^6.3.0",
    "prisma": "^6.3.0"
  }
}
ara_ta3ara_ta3

適当に作ったecサイト用っぽいsql

CREATE TABLE items (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL,
    stock INT DEFAULT 0
);

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL
);

CREATE TABLE current_cart (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL UNIQUE,
    item_id INT NOT NULL,
    quantity INT NOT NULL DEFAULT 1,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (item_id) REFERENCES items(id)
);

CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    total_amount DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE ordered_items (
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_id INT NOT NULL,
    item_id INT NOT NULL,
    quantity INT NOT NULL,
    price_at_purchase DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES orders(id),
    FOREIGN KEY (item_id) REFERENCES items(id)
);

ara_ta3ara_ta3

元のprisma/schema.prisma

generator scala {
  provider = "node ./prisma/scala-generator.js"
  output   = "../dist"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

.env(ローカル用なのでメモがてら載せる

DATABASE_URL="mysql://root:rootpassword@127.0.0.1:3306/ecsite-samples"
ara_ta3ara_ta3

prisma db pull後のprisma/schema.prisma

generator scala {
  provider = "node ./prisma/scala-generator.js"
  output   = "../dist"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model current_cart {
  id       Int   @id @default(autoincrement())
  user_id  Int   @unique(map: "user_id")
  item_id  Int
  quantity Int   @default(1)
  users    users @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "current_cart_ibfk_1")
  items    items @relation(fields: [item_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "current_cart_ibfk_2")

  @@index([item_id], map: "item_id")
}

model items {
  id            Int             @id @default(autoincrement())
  name          String          @db.VarChar(255)
  description   String?         @db.Text
  price         Decimal         @db.Decimal(10, 2)
  stock         Int?            @default(0)
  current_cart  current_cart[]
  ordered_items ordered_items[]
}

model ordered_items {
  id                Int     @id @default(autoincrement())
  order_id          Int
  item_id           Int
  quantity          Int
  price_at_purchase Decimal @db.Decimal(10, 2)
  orders            orders  @relation(fields: [order_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "ordered_items_ibfk_1")
  items             items   @relation(fields: [item_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "ordered_items_ibfk_2")

  @@index([item_id], map: "item_id")
  @@index([order_id], map: "order_id")
}

model orders {
  id            Int             @id @default(autoincrement())
  user_id       Int
  order_date    DateTime?       @default(now()) @db.Timestamp(0)
  total_amount  Decimal         @db.Decimal(10, 2)
  ordered_items ordered_items[]
  users         users           @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "orders_ibfk_1")

  @@index([user_id], map: "user_id")
}

model users {
  id            Int           @id @default(autoincrement())
  username      String        @unique(map: "username") @db.VarChar(255)
  email         String        @unique(map: "email") @db.VarChar(255)
  password_hash String        @db.VarChar(255)
  current_cart  current_cart?
  orders        orders[]
}

ara_ta3ara_ta3

scala-generator.js

#!/usr/bin/env node
const { writeFileSync, mkdirSync } = require("fs");
const path = require("path");
const { generatorHandler } = require("@prisma/generator-helper");

generatorHandler({
  onManifest: () => ({}),

  onGenerate: async (options) => {
    console.log("Prisma Custom Generator: Running...");

    const outputDir = options.generator.output.value;
    mkdirSync(outputDir, { recursive: true });

    for (const model of options.dmmf.datamodel.models) {
      const className = toPascalCase(model.name);
      const fileName = className + ".scala";

      const fields = model.fields.map((field) => {
        return `    ${field.name}: ${
          mapPrismaTypeToScala(field.type, field.isRequired)
        }`;
      }).join(",\n");

      const scalaClass = `
package com.example

case class ${className}(
${fields}
)`;

      const filePath = path.join(outputDir, fileName);
      writeFileSync(filePath, scalaClass);
      console.log(`Generated: ${filePath}`);
    }
  },
});

function mapPrismaTypeToScala(prismaType, isRequired) {
  const typeMapping = {
    Int: "Int",
    String: "String",
    Boolean: "Boolean",
    DateTime: "java.time.LocalDateTime",
    Float: "Double",
    Decimal: "BigDecimal",
  };

  const scalaType = typeMapping[prismaType] || "String";
  return isRequired ? scalaType : `Option[${scalaType}]`;
}

function toPascalCase(str) {
  return str
    .replace(/[_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "")
    .replace(/^./, (m) => m.toUpperCase());
}

ara_ta3ara_ta3

prisma generateで生成されるScalaコードの一部

dist/Item.scala


package com.example

case class Items(
    id: Int,
    name: String,
    description: Option[String],
    price: BigDecimal,
    stock: Option[Int],
    current_cart: String,
    ordered_items: String
)
このスクラップは2ヶ月前にクローズされました