В этой небольшой заметке будет показано, как написать DataGrip расширение для генерации кода (в данном случае POCO (C#) классов) на основе таблиц из почти любой БД (SQL Server, Oracle, DB2, Sybase, MySQL, PostgreSQL, SQLite, Apache Derby, HyperSQL, H2).
Предисловие
DataGrip это относительно новая IDE от JetBrains для работы с разными СУБД имеющая некоторый API для расширения функционала. Задача — используя его написать генератор POCO (C#) классов.
Если вы не хотите читать все это, а желаете просто начать генерировать классы, то вот ссылка на репозиторий со скриптом в заключении.
Написание скрипта
DataGrip позволяет расширить свою функциональность с помощью скриптов (Scripted Extensions). Поддерживаются языки Groovy, Clojure и JavaScript. Документация на сайте об этом довольно краткая, но в распоряжении есть примеры и архив с API в виде исходного кода на Java. Исходный код можно найти в <DataGrip_installation_dir>/lib/src/src_database-openapi.zip
. Примеры можно посмотреть в самой IDE в панели Files -> Scratches. Так же DataGrip поддерживает скрипты для экспорта данных в различные форматы (extractors, в данной статье рассмотрены не будут), примеры для форматов csv, json и html тоже находятся в панели Scratches.
Итак, для написания скрипта мы будем использовать Clojure, за основу был взят пример генератора POJO из IDE.
Подсветки синтаксиса и автодополнения для Clojure в DataGrip конечно же нет, поэтому можно использовать любой другой редактор.
Для начала настроим маппинги типов из БД на C# типы и объявим некоторые константы.
(def usings "using System;")
(def default-type "string")
(def type-mappings
[
[["bit"] "bool"]
[["tinyint"] "byte"]
[["uniqueidentifier"] "Guid"]
[["int"] "long"]
[["char"] "char"]
[["varbinary" "image"] "byte[]" true] ; cannot be null
[["double" "float" "real"] "double"]
[["decimal" "money" "numeric" "smallmoney"] "decimal"]
[["datetime" "timestamp" "date" "time"] "DateTime"]
[["datetimeoffset"] "DateTimeOffset"]
])
(def new-line "rn")
Далее напишем функцию которая приводит строку к PascalCase.
(defn- poco-name [name]
(apply str (map clojure.string/capitalize (re-seq #"(?:[A-Z]+)?[a-zd]*" name))))
Матчинг типа из БД на тип в C# основываясь на маппингах которые мы определили ранее.
(defn- poco-type [data-type is-null]
(let [spec (.. data-type getSpecification toLowerCase)
spec-matches? (fn [pattern] (= (re-find #"^w+" spec) pattern))
mapping-matches? (fn [[ps t n]] (when (some spec-matches? ps) [t n]))
[type cant-be-null] (some mapping-matches? type-mappings)
nullable-type (if (and type (not cant-be-null) is-null) (str type "?") type)]
(or nullable-type default-type)))
Функция которая получает все столбцы из таблицы, вызывает функцию матчинга и собирает нужный нам объект для дальнейшего сохранения. Здесь мы используем методы из API, например com.intellij.database.util.DasUtil/getColumns
, все эти методы можно посмотреть в архиве src_database-openapi.zip
упомянутом выше.
(defn- field-infos [table]
(let [columns (com.intellij.database.util.DasUtil/getColumns table)
field-info (fn [column] {:name (poco-name (.getName column))
:type (poco-type (.getDataType column) (not (.isNotNull column)))})]
(map field-info columns)))
Генерация текста для свойств и классов, ничего особенного просто конкатенация строк. А так же функция записи этого текста в файл.
(defn- property-text [field-info]
(let [type (:type field-info)
name (:name field-info)]
(str " public " type " " name " { get; set; } " new-line)))
(defn- poco-text [class-name fields]
(apply str (flatten
[usings new-line new-line
"public class " class-name " " new-line "{" new-line
(interpose new-line (interleave (map property-text fields)))
"}" new-line])))
(defn- generate-poco [directory table]
(let [class-name (poco-name (.getName table))
fields (field-infos table)
file (java.io.File. directory (str class-name ".cs"))
text (poco-text class-name fields)]
(com.intellij.openapi.util.io.FileUtil/writeToFile file text)))
И наконец функция открытия диалога выбора директории для сохранения файлов и функция которая определяет выбранные таблицы и запускает генерацию.
(defn- generate-pocos [directory]
(let [table? (partial instance? com.intellij.database.model.DasTable)]
(doseq [table (filter table? SELECTION)]
(generate-poco directory table))))
(.chooseDirectoryAndSave FILES
"Choose directory"
"Choose where to generate POCOs to"
(proxy [com.intellij.util.Consumer] []
(consume [directory]
(generate-pocos directory)
(.refresh FILES directory))))
Установка скрипта
Полный код скрипта на GitHub.
Для установки надо просто скопировать файл Generate POCO.clj
в IDE > Files > Scratches > Extensions > DataGrip > schema.
И в контекстном меню таблиц появится соотвествующий пункт подменю в разделе Scripted Extensions.
Результат
Из следующей таблицы
CREATE TABLE Users
(
Id INT PRIMARY KEY NOT NULL IDENTITY,
first_name NVARCHAR(255),
Last_Name NVARCHAR(255),
Email VARCHAR(255) NOT NULL,
UserGuid UNIQUEIDENTIFIER,
Age TINYINT NOT NULL,
Address NVARCHAR(MAX),
photo IMAGE,
Salary MONEY,
ADDITIONAL_INFO NVARCHAR(42)
);
будет сгенерирован следующий класс:
using System;
public class Users
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public Guid? UserGuid { get; set; }
public byte Age { get; set; }
public string Address { get; set; }
public byte[] Photo { get; set; }
public decimal? Salary { get; set; }
public string AdditionalInfo { get; set; }
}
Заключение
Вот таким довольно простым способом можно генерировать C# классы которые описывают ваши таблицы в БД и сделать жизнь чуть менее рутинной. Конечно же, вы не ограничены только POCO классами C#, можно написать генератор чего угодно, для другого языка, фреймворка и т.д.
Автор: BloodUnit